コード例 #1
0
class SearchDocument(Model):
    project = models.ForeignKey(Project)
    group = models.ForeignKey(Group)
    total_events = BoundedPositiveIntegerField(default=1)
    status = BoundedPositiveIntegerField(default=0)
    date_added = models.DateTimeField(default=timezone.now)
    date_changed = models.DateTimeField(default=timezone.now)

    objects = SearchDocumentManager()

    class Meta:
        unique_together = (('project', 'group'), )

    __repr__ = sane_repr('project_id', 'group_id')
コード例 #2
0
class GroupTag(Model):
    """
    Stores the total number of messages seen by a group matching
    the given filter.
    """
    project = models.ForeignKey(Project, null=True)
    group = models.ForeignKey(Group)
    times_seen = BoundedPositiveIntegerField(default=0)
    key = models.CharField(max_length=MAX_TAG_KEY_LENGTH)
    value = models.CharField(max_length=MAX_TAG_VALUE_LENGTH)
    last_seen = models.DateTimeField(default=timezone.now,
                                     db_index=True,
                                     null=True)
    first_seen = models.DateTimeField(default=timezone.now,
                                      db_index=True,
                                      null=True)

    objects = BaseManager()

    class Meta:
        db_table = 'sentry_messagefiltervalue'
        unique_together = (('project', 'key', 'value', 'group'), )

    __repr__ = sane_repr('project_id', 'group_id', 'key', 'value')

    def save(self, *args, **kwargs):
        if not self.first_seen:
            self.first_seen = self.last_seen
        super(GroupTag, self).save(*args, **kwargs)
コード例 #3
0
class SearchToken(Model):
    document = models.ForeignKey(SearchDocument, related_name="token_set")
    field = models.CharField(max_length=64, default='text')
    token = models.CharField(max_length=128)
    times_seen = BoundedPositiveIntegerField(default=1)

    objects = BaseManager()

    class Meta:
        unique_together = (('document', 'field', 'token'), )

    __repr__ = sane_repr('document_id', 'field', 'token')
コード例 #4
0
class Activity(Model):
    COMMENT = 0
    SET_RESOLVED = 1
    SET_UNRESOLVED = 2
    SET_MUTED = 3
    SET_PUBLIC = 4
    SET_PRIVATE = 5
    SET_REGRESSION = 6
    CREATE_ISSUE = 7

    TYPE = (
        # (TYPE, verb-slug)
        (COMMENT, 'comment'),
        (SET_RESOLVED, 'set_resolved'),
        (SET_UNRESOLVED, 'set_unresolved'),
        (SET_MUTED, 'set_muted'),
        (SET_PUBLIC, 'set_public'),
        (SET_PRIVATE, 'set_private'),
        (SET_REGRESSION, 'set_regression'),
        (CREATE_ISSUE, 'create_issue'),
    )

    project = models.ForeignKey(Project)
    group = models.ForeignKey(Group, null=True)
    event = models.ForeignKey(Event, null=True)
    # index on (type, ident)
    type = BoundedPositiveIntegerField(choices=TYPE)
    ident = models.CharField(max_length=64, null=True)
    # if the user is not set, it's assumed to be the system
    user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True)
    datetime = models.DateTimeField(default=timezone.now)
    data = GzippedDictField(null=True)

    __repr__ = sane_repr('project_id', 'group_id', 'event_id', 'user_id',
                         'type', 'ident')

    def save(self, *args, **kwargs):
        created = bool(not self.id)

        super(Activity, self).save(*args, **kwargs)

        if not created:
            return

        # HACK: support Group.num_comments
        if self.type == Activity.COMMENT:
            self.group.update(num_comments=F('num_comments') + 1)

            if self.event:
                self.event.update(num_comments=F('num_comments') + 1)
コード例 #5
0
class TagKey(Model):
    """
    Stores references to available filters keys.
    """
    project = models.ForeignKey(Project)
    key = models.CharField(max_length=32)
    values_seen = BoundedPositiveIntegerField(default=0)

    objects = TagKeyManager()

    class Meta:
        db_table = 'sentry_filterkey'
        unique_together = (('project', 'key'), )

    __repr__ = sane_repr('project_id', 'key')
コード例 #6
0
class GroupTagKey(Model):
    """
    Stores a unique tag key name for a group.

    An example key might be "url" or "server_name".
    """
    project = models.ForeignKey(Project, null=True)
    group = models.ForeignKey(Group)
    key = models.CharField(max_length=MAX_TAG_KEY_LENGTH)
    values_seen = BoundedPositiveIntegerField(default=0)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'group', 'key'), )

    __repr__ = sane_repr('project_id', 'group_id', 'key')
コード例 #7
0
class TagKey(Model):
    """
    Stores references to available filters keys.
    """
    project = models.ForeignKey(Project)
    key = models.CharField(max_length=MAX_TAG_KEY_LENGTH)
    values_seen = BoundedPositiveIntegerField(default=0)
    label = models.CharField(max_length=64, null=True)

    objects = TagKeyManager()

    class Meta:
        db_table = 'sentry_filterkey'
        unique_together = (('project', 'key'), )

    __repr__ = sane_repr('project_id', 'key')

    def get_label(self):
        return self.label or self.key.replace('_', ' ').title()
コード例 #8
0
ファイル: models.py プロジェクト: gelliravi/sentry
class ProjectCountByMinute(Model):
    """
    Stores the total number of messages seen by a project at N minute intervals.

    e.g. if it happened at 08:34:55 the time would be normalized to 08:30:00
    """

    project = models.ForeignKey(Project, null=True)
    date = models.DateTimeField()  # normalized to HH:MM:00
    times_seen = BoundedPositiveIntegerField(default=0)
    time_spent_total = models.FloatField(default=0)
    time_spent_count = BoundedIntegerField(default=0)

    objects = BaseManager()

    class Meta:
        unique_together = (('project', 'date'), )

    __repr__ = sane_repr('project_id', 'date')
コード例 #9
0
ファイル: models.py プロジェクト: nuklea/sentry
class TagValue(Model):
    """
    Stores references to available filters.
    """
    project = models.ForeignKey(Project, null=True)
    key = models.CharField(max_length=MAX_TAG_KEY_LENGTH)
    value = models.CharField(max_length=MAX_TAG_VALUE_LENGTH)
    data = GzippedDictField(blank=True, null=True)
    times_seen = BoundedPositiveIntegerField(default=0)
    last_seen = models.DateTimeField(
        default=timezone.now, db_index=True, null=True)
    first_seen = models.DateTimeField(
        default=timezone.now, db_index=True, null=True)

    objects = BaseManager()

    class Meta:
        db_table = 'sentry_filtervalue'
        unique_together = (('project', 'key', 'value'),)

    __repr__ = sane_repr('project_id', 'key', 'value')
コード例 #10
0
class Group(EventBase):
    """
    Aggregated message which summarizes a set of Events.
    """
    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 = models.FloatField(default=0)
    time_spent_count = BoundedIntegerField(default=0)
    score = BoundedIntegerField(default=0)
    is_public = models.NullBooleanField(default=False, null=True)

    objects = GroupManager()

    class Meta:
        unique_together = (('project', 'checksum'), )
        verbose_name_plural = _('grouped messages')
        verbose_name = _('grouped message')
        permissions = (("can_view", "Can view"), )
        db_table = 'sentry_groupedmessage'

    __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 delete(self):
        model_list = (GroupTagKey, GroupTag, GroupCountByMinute, EventMapping,
                      Event)
        for model in model_list:
            logging.info('Removing %r objects where group=%s', model, self.id)
            has_results = True
            while has_results:
                has_results = False
                for obj in model.objects.filter(group=self)[:1000]:
                    obj.delete()
                    has_results = True
        super(Group, self).delete()

    def get_absolute_url(self):
        return absolute_uri(
            reverse('sentry-group',
                    args=[self.team.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.logger, self.culprit, 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):
        if not hasattr(self, '_latest_event'):
            try:
                self._latest_event = self.event_set.order_by('-datetime')[0]
            except IndexError:
                self._latest_event = None
        return self._latest_event

    def get_version(self):
        if not self.data:
            return
        if 'version' not in self.data:
            return
        module = self.data.get('module', 'ver')
        return module, self.data['version']

    def get_unique_tags(self, tag):
        return self.grouptag_set.filter(
            project=self.project,
            key=tag,
        ).values_list(
            'value',
            'times_seen',
            'first_seen',
            'last_seen',
        ).order_by('-times_seen')

    def get_tags(self):
        if not hasattr(self, '_tag_cache'):
            self._tag_cache = sorted([
                t for t in self.grouptagkey_set.filter(
                    project=self.project, ).values_list('key', flat=True)
                if not t.startswith('sentry:')
            ])
        return self._tag_cache
コード例 #11
0
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
コード例 #12
0
class Project(Model):
    """
    Projects are permission based namespaces which generally
    are the top level entry point for all data.

    A project may be owned by only a single team, and may or may not
    have an owner (which is thought of as a project creator).
    """
    PLATFORM_CHOICES = tuple((p, PLATFORM_TITLES.get(p, p.title()))
                             for p in PLATFORM_LIST) + (('other', 'Other'), )

    slug = models.SlugField(null=True)
    name = models.CharField(max_length=200)
    owner = models.ForeignKey(settings.AUTH_USER_MODEL,
                              related_name="sentry_owned_project_set",
                              null=True)
    team = models.ForeignKey(Team, null=True)
    public = models.BooleanField(default=False)
    date_added = models.DateTimeField(default=timezone.now)
    status = BoundedPositiveIntegerField(default=0,
                                         choices=(
                                             (STATUS_VISIBLE, _('Visible')),
                                             (STATUS_HIDDEN, _('Hidden')),
                                         ),
                                         db_index=True)
    platform = models.CharField(max_length=32,
                                choices=PLATFORM_CHOICES,
                                null=True)

    objects = ProjectManager(cache_fields=[
        'pk',
        'slug',
    ])

    class Meta:
        unique_together = (('team', 'slug'), )

    __repr__ = sane_repr('team_id', 'slug', 'owner_id')

    def __unicode__(self):
        return u'%s (%s)' % (self.name, self.slug)

    def save(self, *args, **kwargs):
        if not self.slug:
            slugify_instance(self, self.name, team=self.team)
        super(Project, self).save(*args, **kwargs)

    def delete(self):
        # This handles cascades properly
        # TODO: this doesn't clean up the index
        for model in (TagKey, TagValue, GroupTagKey, GroupTag,
                      GroupCountByMinute, ProjectCountByMinute, Activity,
                      EventMapping, Event, Group):
            logging.info('Removing %r objects where project=%s', model,
                         self.id)
            has_results = True
            while has_results:
                has_results = False
                for obj in model.objects.filter(project=self)[:1000]:
                    obj.delete()
                    has_results = True
        super(Project, self).delete()

    def get_absolute_url(self):
        return absolute_uri(
            reverse('sentry-stream', args=[self.team.slug, self.slug]))

    def merge_to(self, project):
        if not isinstance(project, Project):
            project = Project.objects.get_from_cache(pk=project)

        for group in Group.objects.filter(project=self):
            try:
                other = Group.objects.get(
                    project=project,
                    logger=group.logger,
                    culprit=group.culprit,
                    checksum=group.checksum,
                )
            except Group.DoesNotExist:
                group.update(project=project)
                for model in (Event, GroupTag, GroupCountByMinute):
                    model.objects.filter(project=self,
                                         group=group).update(project=project)
            else:
                Event.objects.filter(group=group).update(group=other)

                for obj in GroupTag.objects.filter(group=group):
                    obj2, created = GroupTag.objects.get_or_create(
                        project=project,
                        group=group,
                        key=obj.key,
                        value=obj.value,
                        defaults={'times_seen': obj.times_seen})
                    if not created:
                        obj2.update(times_seen=F('times_seen') +
                                    obj.times_seen)

                for obj in GroupCountByMinute.objects.filter(group=group):
                    obj2, created = GroupCountByMinute.objects.get_or_create(
                        project=project,
                        group=group,
                        date=obj.date,
                        defaults={
                            'times_seen': obj.times_seen,
                            'time_spent_total': obj.time_spent_total,
                            'time_spent_count': obj.time_spent_count,
                        })
                    if not created:
                        obj2.update(
                            times_seen=F('times_seen') + obj.times_seen,
                            time_spent_total=F('time_spent_total') +
                            obj.time_spent_total,
                            time_spent_count=F('time_spent_count') +
                            obj.time_spent_count,
                        )

        for fv in TagValue.objects.filter(project=self):
            TagValue.objects.get_or_create(project=project,
                                           key=fv.key,
                                           value=fv.value)
            fv.delete()
        self.delete()

    def is_default_project(self):
        return str(self.id) == str(settings.SENTRY_PROJECT) or str(
            self.slug) == str(settings.SENTRY_PROJECT)

    def get_tags(self):
        if not hasattr(self, '_tag_cache'):
            tags = ProjectOption.objects.get_value(self, 'tags', None)
            if tags is None:
                tags = TagKey.objects.all_keys(self)
            self._tag_cache = [t for t in tags if not t.startswith('sentry:')]
        return self._tag_cache

    # TODO: Make these a mixin
    def update_option(self, *args, **kwargs):
        return ProjectOption.objects.set_value(self, *args, **kwargs)

    def get_option(self, *args, **kwargs):
        return ProjectOption.objects.get_value(self, *args, **kwargs)
コード例 #13
0
class Alert(Model):
    project = models.ForeignKey(Project)
    group = models.ForeignKey(Group, null=True)
    datetime = models.DateTimeField(default=timezone.now)
    message = models.TextField()
    data = GzippedDictField(null=True)
    related_groups = models.ManyToManyField(Group,
                                            through='sentry.AlertRelatedGroup',
                                            related_name='related_alerts')
    status = BoundedPositiveIntegerField(default=0,
                                         choices=(
                                             (STATUS_UNRESOLVED,
                                              _('Unresolved')),
                                             (STATUS_RESOLVED, _('Resolved')),
                                         ),
                                         db_index=True)

    __repr__ = sane_repr('project_id', 'group_id', 'datetime')

    # TODO: move classmethods to manager
    @classmethod
    def get_recent_for_project(cls, project_id):
        return cls.objects.filter(
            project=project_id,
            group_id__isnull=True,
            datetime__gte=timezone.now() - timedelta(minutes=60),
            status=STATUS_UNRESOLVED,
        ).order_by('-datetime')

    @classmethod
    def maybe_alert(cls, project_id, message, group_id=None):
        now = timezone.now()
        manager = cls.objects
        # We only create an alert based on:
        # - an alert for the project hasn't been created in the last 30 minutes
        # - an alert for the event hasn't been created in the last 60 minutes

        # TODO: there is a race condition if we're calling this function for the same project
        if manager.filter(project=project_id,
                          datetime__gte=now - timedelta(minutes=60)).exists():
            return

        if manager.filter(project=project_id,
                          group=group_id,
                          datetime__gte=now - timedelta(minutes=60)).exists():
            return

        alert = manager.create(
            project_id=project_id,
            group_id=group_id,
            datetime=now,
            message=message,
        )

        if not group_id and has_trending():
            # Capture the top 5 trending events at the time of this error
            related_groups = Group.objects.get_accelerated(
                [project_id], minutes=MINUTE_NORMALIZATION)[:5]
            for group in related_groups:
                AlertRelatedGroup.objects.create(
                    group=group,
                    alert=alert,
                )

        return alert

    @property
    def team(self):
        return self.project.team

    @property
    def is_resolved(self):
        return (self.status == STATUS_RESOLVED
                or self.datetime < timezone.now() - timedelta(minutes=60))

    def get_absolute_url(self):
        return absolute_uri(
            reverse('sentry-alert-details',
                    args=[self.team.slug, self.project.slug, self.id]))
コード例 #14
0
ファイル: tests.py プロジェクト: xssworm/sentry
class DummyModel(Model):
    foo = models.CharField(max_length=32)
    normint = BoundedIntegerField(null=True)
    bigint = BoundedBigIntegerField(null=True)
    posint = BoundedPositiveIntegerField(null=True)