def test_resolved_in_next_release(self): self.login_as(user=self.user) project = self.create_project() project.flags.has_releases = True project.save() group = self.create_group(project=project) Release.get_or_create( version='abcd', project=project, ) url = '/api/0/issues/{}/'.format(group.id) response = self.client.put( url, data={ 'status': 'resolvedInNextRelease', } ) assert response.status_code == 200, response.content group = Group.objects.get( id=group.id, project=group.project.id, ) assert group.status == GroupStatus.RESOLVED assert GroupResolution.objects.filter( group=group, ).exists()
def finish_release(self, version, **values): if not Release.is_valid_version(version): raise HookValidationError('Invalid release version: %s' % version) values.setdefault('date_released', timezone.now()) try: with transaction.atomic(): release = Release.objects.create( version=version, organization_id=self.project.organization_id, **values ) except IntegrityError: release = Release.objects.get( version=version, organization_id=self.project.organization_id, ) release.update(**values) release.add_project(self.project) Activity.objects.create( type=Activity.RELEASE, project=self.project, ident=Activity.get_version_ident(version), data={'version': version}, datetime=values['date_released'], ) self.set_refs(release=release, **values)
def start_release(self, version, **values): values.setdefault('date_started', timezone.now()) affected = Release.objects.filter( version=version, organization_id=self.project.organization_id, projects=self.project, ).update(**values) if not affected: release = Release.objects.filter( version=version, organization_id=self.project.organization_id, ).first() if release: release.update(**values) else: lock_key = Release.get_lock_key(self.project.organization_id, version) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release = Release.objects.get( version=version, organization_id=self.project.organization_id ) except Release.DoesNotExist: release = Release.objects.create( version=version, organization_id=self.project.organization_id, **values ) release.add_project(self.project)
def get_release(self, project, data): if not data.get('release'): return return Release.get( project=project, version=data['release'], )
def get_release(self, create=False): """Convenient helper to return the release for the current data and optionally creates the release if it's missing. In case there is no release info it will return `None`. """ release = self.data.get('release') if not release: return None if not create: return Release.get(project=self.project, version=self.data['release']) timestamp = self.data.get('timestamp') if timestamp is not None: date = datetime.fromtimestamp(timestamp).replace(tzinfo=timezone.utc) else: date = None return Release.get_or_create( project=self.project, version=self.data['release'], date_added=date, )
def preprocess_step(self, processing_task): frames = self.get_valid_frames() if not frames: logger.debug('Event %r has no frames with enough context to ' 'fetch remote source', self.data['event_id']) return False if self.data.get('release'): self.release = Release.get( project=self.project, version=self.data['release'], ) self.populate_source_cache(frames) return True
def start_release(self, version, **values): if not Release.is_valid_version(version): raise HookValidationError('Invalid release version: %s' % version) try: with transaction.atomic(): release = Release.objects.create( version=version, organization_id=self.project.organization_id, **values ) except IntegrityError: release = Release.objects.get( version=version, organization_id=self.project.organization_id, ) release.update(**values) release.add_project(self.project)
def make_release_generator(): id_sequence = itertools.count(1) while True: dt = to_datetime( random.randint( timestamp - (30 * 24 * 60 * 60), timestamp, ), ) p = random.choice(projects) yield Release( id=next(id_sequence), project=p, organization_id=p.organization_id, version=''.join( [random.choice('0123456789abcdef') for _ in range(40)]), date_added=dt, )
def get(self, request): org = Organization( id=1, slug='organization', name='My Company', ) team = Team( id=1, slug='team', name='My Team', organization=org, ) project = Project( id=1, organization=org, team=team, slug='project', name='My Project', ) release = Release( project=project, version=sha1(uuid4().bytes).hexdigest(), ) release_link = absolute_uri('/{}/{}/releases/{}/'.format( org.slug, project.slug, release.version, )) project_link = absolute_uri('/{}/{}/'.format( org.slug, project.slug, )) return MailPreview( html_template='sentry/emails/activity/release.html', text_template='sentry/emails/activity/release.txt', context={ 'release': release, 'project': project, 'release_link': release_link, 'project_link': project_link, }, ).render(request)
def get_tag_value_label(self, key, value): label = value if key == "sentry:user": if value.startswith("id:"): label = value[len("id:"):] elif value.startswith("email:"): label = value[len("email:"):] elif value.startswith("username:"******"username:"******"ip:"): label = value[len("ip:"):] elif key == "sentry:release": from sentry.models import Release label = Release.get_display_version(value) return label
def get_tag_value_label(self, key, value): label = value if key == 'sentry:user': if value.startswith('id:'): label = value[len('id:'):] elif value.startswith('email:'): label = value[len('email:'):] elif value.startswith('username:'******'username:'******'ip:'): label = value[len('ip:'):] elif key == 'sentry:release': from sentry.models import Release label = Release.get_display_version(value) return label
def debounce_update_release_health_data(organization, project_ids): """This causes a flush of snuba health data to the postgres tables once per minute for the given projects. """ # Figure out which projects need to get updates from the snuba. should_update = {} cache_keys = ["debounce-health:%d" % id for id in project_ids] cache_data = cache.get_many(cache_keys) for project_id, cache_key in izip(project_ids, cache_keys): if cache_data.get(cache_key) is None: should_update[project_id] = cache_key if not should_update: return projects = { p.id: p for p in Project.objects.get_many_from_cache(should_update.keys()) } # This gives us updates for all release-projects which have seen new # health data over the last 24 hours. It will miss releases where the last # date is <24h ago. We need to aggregate the data for the totals per release # manually here now. This does not take environments into account. for project_id, version in get_changed_project_release_model_adoptions( should_update.keys()): project = projects.get(project_id) if project is None: # should not happen continue # We might have never observed the release. This for instance can # happen if the release only had health data so far. For these cases # we want to create the release the first time we observed it on the # health side. release = Release.get_or_create(project=project, version=version) # Make sure that the release knows about this project. Like we had before # the project might not have been associated with this release yet. release.add_project(project) # Debounce updates for a minute cache.set_many( dict(izip(should_update.values(), [True] * len(should_update))), 60)
def test_transfer_to_team_releases(self): from_org = self.create_organization() from_team = self.create_team(organization=from_org) to_org = self.create_organization() to_team = self.create_team(organization=to_org) project = self.create_project(teams=[from_team]) environment = Environment.get_or_create(project, 'production') release = Release.get_or_create(project=project, version='1.0') ReleaseProjectEnvironment.objects.create( project=project, release=release, environment=environment, ) assert ReleaseProjectEnvironment.objects.filter( project=project, release=release, environment=environment, ).exists() assert ReleaseProject.objects.filter( project=project, release=release, ).exists() project.transfer_to(team=to_team) project = Project.objects.get(id=project.id) assert project.teams.count() == 1 assert project.teams.first() == to_team assert project.organization_id == to_org.id assert not ReleaseProjectEnvironment.objects.filter( project=project, release=release, environment=environment, ).exists() assert not ReleaseProject.objects.filter( project=project, release=release, ).exists()
def set_commits(self, version, commit_list): """ Commits should be ordered oldest to newest. Calling this method will remove all existing commit history. """ if not Release.is_valid_version(version): raise HookValidationError("Invalid release version: %s" % version) project = self.project try: with transaction.atomic(): release = Release.objects.create( organization_id=project.organization_id, version=version) except IntegrityError: release = Release.objects.get( organization_id=project.organization_id, version=version) release.add_project(project) release.set_commits(commit_list)
def set_commits(self, version, commit_list): """ Commits should be ordered oldest to newest. Calling this method will remove all existing commit history. """ if not Release.is_valid_version(version): raise HookValidationError('Invalid release version: %s' % version) project = self.project try: with transaction.atomic(): release = Release.objects.create( organization_id=project.organization_id, version=version ) except IntegrityError: release = Release.objects.get(organization_id=project.organization_id, version=version) release.add_project(project) release.set_commits(commit_list)
def get_attrs(self, item_list, user): result = {} for item in item_list: label = item.value if item.key == 'sentry:user': if item.value.startswith('id:'): label = item.value[len('id:'):] elif item.value.startswith('email:'): label = item.value[len('email:'):] elif item.value.startswith('username:'******'username:'******'ip:'): label = item.value[len('ip:'):] elif item.key == 'sentry:release': label = Release.get_display_version(item.value) result[item] = { 'name': label, } return result
def finish_release(self, version, **values): values.setdefault('date_released', timezone.now()) affected = Release.objects.filter( version=version, organization_id=self.project.organization_id, projects=self.project, ).update(**values) if not affected: release = Release.objects.filter( version=version, organization_id=self.project.organization_id, ).first() if release: release.update(**values) else: lock_key = Release.get_lock_key(self.project.organization_id, version) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release = Release.objects.get( version=version, organization_id=self.project.organization_id, ) except Release.DoesNotExist: release = Release.objects.create( version=version, organization_id=self.project.organization_id, **values) release.add_project(self.project) activity = Activity.objects.create( type=Activity.RELEASE, project=self.project, ident=version, data={'version': version}, datetime=values['date_released'], ) activity.send_notification()
def finish_release(self, version, **values): values.setdefault('date_released', timezone.now()) affected = Release.objects.filter( version=version, organization_id=self.project.organization_id, projects=self.project, ).update(**values) if not affected: release = Release.objects.filter( version=version, organization_id=self.project.organization_id, ).first() if release: release.update(**values) else: lock_key = Release.get_lock_key(self.project.organization_id, version) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release = Release.objects.get( version=version, organization_id=self.project.organization_id, ) except Release.DoesNotExist: release = Release.objects.create( version=version, organization_id=self.project.organization_id, **values ) release.add_project(self.project) activity = Activity.objects.create( type=Activity.RELEASE, project=self.project, ident=version, data={'version': version}, datetime=values['date_released'], ) activity.send_notification()
def test_commits_lock_conflict(self): user = self.create_user(is_staff=False, is_superuser=False) org = self.create_organization() org.flags.allow_joinleave = False org.save() team = self.create_team(organization=org) project = self.create_project(name="foo", organization=org, teams=[team]) self.create_member(teams=[team], user=user, organization=org) self.login_as(user=user) release = self.create_release(project, self.user, version="1.2.1") release.add_project(project) # Simulate a concurrent request by using an existing release # that has its commit lock taken out. lock = locks.get(Release.get_lock_key(org.id, release.id), duration=10) lock.acquire() url = reverse( "sentry-api-0-organization-release-details", kwargs={ "organization_slug": org.slug, "version": release.version }, ) response = self.client.put( url, data={"commits": [{ "id": "a" * 40 }, { "id": "b" * 40 }]}) assert response.status_code == 409, (response.status_code, response.content) assert "Release commits" in response.data["detail"]
def test_simple(self): date = datetime.datetime.utcnow() org = self.create_organization() project = self.create_project(organization=org, name='foo') # this shouldn't be included release1 = Release.objects.create( organization=org, version='a' * 40, date_released=date - datetime.timedelta(days=2), ) release1.add_project(project) release2 = Release.objects.create( organization=org, version='b' * 40, date_released=date - datetime.timedelta(days=1), ) release2.add_project(project) release3 = Release.objects.create( organization=org, version='c' * 40, date_released=date, ) release3.add_project(project) releases = list(Release.get_closest_releases(project, release2.version)) assert len(releases) == 2 assert releases[0] == release2 assert releases[1] == release3
def _get_or_create_release_many(jobs, projects): jobs_with_releases = {} release_date_added = {} for job in jobs: if not job["release"]: continue release_key = (job["project_id"], job["release"]) jobs_with_releases.setdefault(release_key, []).append(job) new_datetime = job["event"].datetime old_datetime = release_date_added.get(release_key) if old_datetime is None or new_datetime > old_datetime: release_date_added[release_key] = new_datetime for (project_id, version), jobs_to_update in six.iteritems(jobs_with_releases): release = Release.get_or_create( project=projects[project_id], version=version, date_added=release_date_added[(project_id, version)], ) for job in jobs_to_update: # dont allow a conflicting 'release' tag data = job["data"] pop_tag(data, "release") set_tag(data, "sentry:release", release.version) job["release"] = release if job["dist"]: job["dist"] = job["release"].add_dist(job["dist"], job["event"].datetime) # dont allow a conflicting 'dist' tag pop_tag(job["data"], "dist") set_tag(job["data"], "sentry:dist", job["dist"].name)
def get_latest_release( projects: Sequence[Project | int], environments: Optional[Sequence[Environment]], organization_id: Optional[int] = None, ) -> Sequence[str]: if organization_id is None: project = projects[0] if hasattr(project, "organization_id"): organization_id = project.organization_id else: return [] # Convert projects to ids so that we can work with them more easily project_ids = [getattr(project, "id", project) for project in projects] semver_project_ids = [] date_project_ids = [] for project_id in project_ids: if follows_semver_versioning_scheme(organization_id, project_id): semver_project_ids.append(project_id) else: date_project_ids.append(project_id) versions = set() versions.update( _run_latest_release_query(LatestReleaseOrders.SEMVER, semver_project_ids, environments, organization_id)) versions.update( _run_latest_release_query(LatestReleaseOrders.DATE, date_project_ids, environments, organization_id)) if not versions: raise Release.DoesNotExist() return list(sorted(versions))
def ensure_release_exists(instance, created, **kwargs): if instance.key != 'sentry:release': return if instance.data and instance.data.get('release_id'): return affected = Release.objects.filter( organization_id=instance.project.organization_id, version=instance.value, projects=instance.project ).update(date_added=instance.first_seen) if not affected: release = Release.objects.filter( organization_id=instance.project.organization_id, version=instance.value ).first() if release: release.update(date_added=instance.first_seen) else: lock_key = Release.get_lock_key(instance.project.organization_id, instance.value) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release = Release.objects.get( organization_id=instance.project.organization_id, version=instance.value, ) except Release.DoesNotExist: release = Release.objects.create( organization_id=instance.project.organization_id, version=instance.value, date_added=instance.first_seen, ) instance.update(data={'release_id': release.id}) release.add_project(instance.project)
def setUp(self): self.project = self.create_project() self.release = Release.get_or_create(self.project, '1.0') self.environment1 = Environment.get_or_create(self.project, 'prod') self.environment2 = Environment.get_or_create(self.project, 'staging') self.timestamp = 1403007314
def save(self, project, raw=False): from sentry.tasks.post_process import index_event_tags project = Project.objects.get_from_cache(id=project) data = self.data.copy() # First we pull out our top-level (non-data attr) kwargs event_id = data.pop('event_id') level = data.pop('level') culprit = data.pop('culprit', None) logger_name = data.pop('logger', None) server_name = data.pop('server_name', None) site = data.pop('site', None) checksum = data.pop('checksum', None) fingerprint = data.pop('fingerprint', None) platform = data.pop('platform', None) release = data.pop('release', None) environment = data.pop('environment', None) # unused time_spent = data.pop('time_spent', None) message = data.pop('message', '') if not culprit: # if we generate an implicit culprit, lets not call it a # transaction transaction_name = None culprit = generate_culprit(data, platform=platform) else: transaction_name = culprit date = datetime.fromtimestamp(data.pop('timestamp')) date = date.replace(tzinfo=timezone.utc) kwargs = { 'platform': platform, } event = Event( project_id=project.id, event_id=event_id, data=data, time_spent=time_spent, datetime=date, **kwargs ) # convert this to a dict to ensure we're only storing one value per key # as most parts of Sentry dont currently play well with multiple values tags = dict(data.get('tags') or []) tags['level'] = LOG_LEVELS[level] if logger_name: tags['logger'] = logger_name if server_name: tags['server_name'] = server_name if site: tags['site'] = site if environment: tags['environment'] = environment if transaction_name: tags['transaction'] = transaction_name if release: # dont allow a conflicting 'release' tag if 'release' in tags: del tags['release'] tags['sentry:release'] = release event_user = self._get_event_user(project, data) if event_user: # dont allow a conflicting 'user' tag if 'user' in tags: del tags['user'] tags['sentry:user'] = event_user.tag_value for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: # plugins should not override user provided tags for key, value in added_tags: tags.setdefault(key, value) # tags are stored as a tuple tags = tags.items() # XXX(dcramer): we're relying on mutation of the data object to ensure # this propagates into Event data['tags'] = tags data['fingerprint'] = fingerprint or ['{{ default }}'] for path, iface in six.iteritems(event.interfaces): data['tags'].extend(iface.iter_tags()) # Get rid of ephemeral interface data if iface.ephemeral: data.pop(iface.get_path(), None) # prioritize fingerprint over checksum as its likely the client defaulted # a checksum whereas the fingerprint was explicit if fingerprint: hashes = [ md5_from_hash(h) for h in get_hashes_from_fingerprint(event, fingerprint) ] elif checksum: hashes = [checksum] data['checksum'] = checksum else: hashes = [ md5_from_hash(h) for h in get_hashes_for_event(event) ] # TODO(dcramer): temp workaround for complexity data['message'] = message event_type = eventtypes.get(data.get('type', 'default'))(data) event_metadata = event_type.get_metadata() # TODO(dcramer): temp workaround for complexity del data['message'] data['type'] = event_type.key data['metadata'] = event_metadata # index components into ``Event.message`` # See GH-3248 if event_type.key != 'default': if 'sentry.interfaces.Message' in data and \ data['sentry.interfaces.Message']['message'] != message: message = u'{} {}'.format( message, data['sentry.interfaces.Message']['message'], ) if not message: message = '' elif not isinstance(message, six.string_types): message = force_text(message) for value in six.itervalues(event_metadata): value_u = force_text(value, errors='replace') if value_u not in message: message = u'{} {}'.format(message, value_u) if culprit and culprit not in message: culprit_u = force_text(culprit, errors='replace') message = u'{} {}'.format(message, culprit_u) message = trim(message.strip(), settings.SENTRY_MAX_MESSAGE_LENGTH) event.message = message kwargs['message'] = message group_kwargs = kwargs.copy() group_kwargs.update({ 'culprit': culprit, 'logger': logger_name, 'level': level, 'last_seen': date, 'first_seen': date, 'active_at': date, 'data': { 'last_received': event.data.get('received') or float(event.datetime.strftime('%s')), 'type': event_type.key, # we cache the events metadata on the group to ensure its # accessible in the stream 'metadata': event_metadata, }, }) if release: release = Release.get_or_create( project=project, version=release, date_added=date, ) group_kwargs['first_release'] = release group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **group_kwargs ) event.group = group # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) try: with transaction.atomic(using=router.db_for_write(EventMapping)): EventMapping.objects.create( project=project, group=group, event_id=event_id) except IntegrityError: self.logger.info('duplicate.found', extra={'event_id': event.id}, exc_info=True) return event environment = Environment.get_or_create( project=project, name=environment, ) if release: ReleaseEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) grouprelease = GroupRelease.get_or_create( group=group, release=release, environment=environment, datetime=date, ) counters = [ (tsdb.models.group, group.id), (tsdb.models.project, project.id), ] if release: counters.append((tsdb.models.release, release.id)) tsdb.incr_multi(counters, timestamp=event.datetime) frequencies = [ # (tsdb.models.frequent_projects_by_organization, { # project.organization_id: { # project.id: 1, # }, # }), # (tsdb.models.frequent_issues_by_project, { # project.id: { # group.id: 1, # }, # }) (tsdb.models.frequent_environments_by_group, { group.id: { environment.id: 1, }, }) ] if release: frequencies.append( (tsdb.models.frequent_releases_by_group, { group.id: { grouprelease.id: 1, }, }) ) tsdb.record_frequency_multi(frequencies, timestamp=event.datetime) UserReport.objects.filter( project=project, event_id=event_id, ).update(group=group) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(using=router.db_for_write(Event)): event.save() except IntegrityError: self.logger.info('duplicate.found', extra={'event_id': event.id}, exc_info=True) return event index_event_tags.delay( project_id=project.id, group_id=group.id, event_id=event.id, tags=tags, ) if event_user: tsdb.record_multi(( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value,)), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value,)), ), timestamp=event.datetime) if is_new and release: buffer.incr(Release, {'new_groups': 1}, { 'id': release.id, }) safe_execute(Group.objects.add_tags, group, tags, _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) first_event_received.send(project=project, group=group, sender=Project) post_process_group.delay( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, ) else: self.logger.info('post_process.skip.raw_event', extra={'event_id': event.id}) # TODO: move this to the queue if is_regression and not raw: regression_signal.send_robust(sender=Group, instance=group) return event
def save(self, project_id, raw=False, assume_normalized=False, cache_key=None): """ We re-insert events with duplicate IDs into Snuba, which is responsible for deduplicating events. Since deduplication in Snuba is on the primary key (based on event ID, project ID and day), events with same IDs are only deduplicated if their timestamps fall on the same day. The latest event always wins and overwrites the value of events received earlier in that day. Since we increment counters and frequencies here before events get inserted to eventstream these numbers may be larger than the total number of events if we receive duplicate event IDs that fall on the same day (that do not hit cache first). """ # Normalize if needed if not self._normalized: if not assume_normalized: self.normalize() self._normalized = True data = self._data project = Project.objects.get_from_cache(id=project_id) project._organization_cache = Organization.objects.get_from_cache( id=project.organization_id) # Pull out the culprit culprit = self.get_culprit() # Pull the toplevel data we're interested in level = data.get("level") # TODO(mitsuhiko): this code path should be gone by July 2018. # This is going to be fine because no code actually still depends # on integers here. When we need an integer it will be converted # into one later. Old workers used to send integers here. if level is not None and isinstance(level, six.integer_types): level = LOG_LEVELS[level] transaction_name = data.get("transaction") logger_name = data.get("logger") release = data.get("release") dist = data.get("dist") environment = data.get("environment") recorded_timestamp = data.get("timestamp") # We need to swap out the data with the one internal to the newly # created event object event = self._get_event_instance(project_id=project_id) self._data = data = event.data.data event._project_cache = project date = event.datetime platform = event.platform event_id = event.event_id if transaction_name: transaction_name = force_text(transaction_name) # Right now the event type is the signal to skip the group. This # is going to change a lot. if event.get_event_type() == "transaction": issueless_event = True else: issueless_event = False # Some of the data that are toplevel attributes are duplicated # into tags (logger, level, environment, transaction). These are # different from legacy attributes which are normalized into tags # ahead of time (site, server_name). setdefault_path(data, "tags", value=[]) set_tag(data, "level", level) if logger_name: set_tag(data, "logger", logger_name) if environment: set_tag(data, "environment", environment) if transaction_name: set_tag(data, "transaction", transaction_name) if release: # dont allow a conflicting 'release' tag pop_tag(data, "release") release = Release.get_or_create(project=project, version=release, date_added=date) set_tag(data, "sentry:release", release.version) if dist and release: dist = release.add_dist(dist, date) # dont allow a conflicting 'dist' tag pop_tag(data, "dist") set_tag(data, "sentry:dist", dist.name) else: dist = None event_user = self._get_event_user(project, data) if event_user: # dont allow a conflicting 'user' tag pop_tag(data, "user") set_tag(data, "sentry:user", event_user.tag_value) # At this point we want to normalize the in_app values in case the # clients did not set this appropriately so far. grouping_config = load_grouping_config( get_grouping_config_dict_for_event_data(data, project)) normalize_stacktraces_for_grouping(data, grouping_config) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: # plugins should not override user provided tags for key, value in added_tags: if get_tag(data, key) is None: set_tag(data, key, value) for path, iface in six.iteritems(event.interfaces): for k, v in iface.iter_tags(): set_tag(data, k, v) # Get rid of ephemeral interface data if iface.ephemeral: data.pop(iface.path, None) # The active grouping config was put into the event in the # normalize step before. We now also make sure that the # fingerprint was set to `'{{ default }}' just in case someone # removed it from the payload. The call to get_hashes will then # look at `grouping_config` to pick the right parameters. data["fingerprint"] = data.get("fingerprint") or ["{{ default }}"] apply_server_fingerprinting( data, get_fingerprinting_config_for_project(project)) # Here we try to use the grouping config that was requested in the # event. If that config has since been deleted (because it was an # experimental grouping config) we fall back to the default. try: hashes = event.get_hashes() except GroupingConfigNotFound: data["grouping_config"] = get_grouping_config_dict_for_project( project) hashes = event.get_hashes() data["hashes"] = hashes # we want to freeze not just the metadata and type in but also the # derived attributes. The reason for this is that we push this # data into kafka for snuba processing and our postprocessing # picks up the data right from the snuba topic. For most usage # however the data is dynamically overridden by Event.title and # Event.location (See Event.as_dict) materialized_metadata = self.materialize_metadata() data.update(materialized_metadata) data["culprit"] = culprit received_timestamp = event.data.get("received") or float( event.datetime.strftime("%s")) if not issueless_event: # The group gets the same metadata as the event when it's flushed but # additionally the `last_received` key is set. This key is used by # _save_aggregate. group_metadata = dict(materialized_metadata) group_metadata["last_received"] = received_timestamp kwargs = { "platform": platform, "message": event.search_message, "culprit": culprit, "logger": logger_name, "level": LOG_LEVELS_MAP.get(level), "last_seen": date, "first_seen": date, "active_at": date, "data": group_metadata, } if release: kwargs["first_release"] = release try: group, is_new, is_regression = self._save_aggregate( event=event, hashes=hashes, release=release, **kwargs) except HashDiscarded: event_discarded.send_robust(project=project, sender=EventManager) metrics.incr( "events.discarded", skip_internal=True, tags={ "organization_id": project.organization_id, "platform": platform }, ) raise else: event_saved.send_robust(project=project, event_size=event.size, sender=EventManager) event.group = group else: group = None is_new = False is_regression = False event_saved.send_robust(project=project, event_size=event.size, sender=EventManager) # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) environment = Environment.get_or_create(project=project, name=environment) if group: group_environment, is_new_group_environment = GroupEnvironment.get_or_create( group_id=group.id, environment_id=environment.id, defaults={"first_release": release if release else None}, ) else: is_new_group_environment = False if release: ReleaseEnvironment.get_or_create(project=project, release=release, environment=environment, datetime=date) ReleaseProjectEnvironment.get_or_create(project=project, release=release, environment=environment, datetime=date) if group: grouprelease = GroupRelease.get_or_create( group=group, release=release, environment=environment, datetime=date) counters = [(tsdb.models.project, project.id)] if group: counters.append((tsdb.models.group, group.id)) if release: counters.append((tsdb.models.release, release.id)) tsdb.incr_multi(counters, timestamp=event.datetime, environment_id=environment.id) frequencies = [] if group: frequencies.append((tsdb.models.frequent_environments_by_group, { group.id: { environment.id: 1 } })) if release: frequencies.append((tsdb.models.frequent_releases_by_group, { group.id: { grouprelease.id: 1 } })) if frequencies: tsdb.record_frequency_multi(frequencies, timestamp=event.datetime) if group: UserReport.objects.filter(project=project, event_id=event_id).update( group=group, environment=environment) # Enusre the _metrics key exists. This is usually created during # and prefilled with ingestion sizes. event_metrics = event.data.get("_metrics") or {} event.data["_metrics"] = event_metrics # Capture the actual size that goes into node store. event_metrics["bytes.stored.event"] = len( json.dumps(dict(event.data.items()))) # Load attachments first, but persist them at the very last after # posting to eventstream to make sure all counters and eventstream are # incremented for sure. attachments = self.get_attachments(cache_key, event) for attachment in attachments: key = "bytes.stored.%s" % (attachment.type, ) event_metrics[key] = (event_metrics.get(key) or 0) + len( attachment.data) # Write the event to Nodestore event.data.save() if event_user: counters = [(tsdb.models.users_affected_by_project, project.id, (event_user.tag_value, ))] if group: counters.append((tsdb.models.users_affected_by_group, group.id, (event_user.tag_value, ))) tsdb.record_multi(counters, timestamp=event.datetime, environment_id=environment.id) if release: if is_new: buffer.incr( ReleaseProject, {"new_groups": 1}, { "release_id": release.id, "project_id": project.id }, ) if is_new_group_environment: buffer.incr( ReleaseProjectEnvironment, {"new_issues_count": 1}, { "project_id": project.id, "release_id": release.id, "environment_id": environment.id, }, ) if not raw: if not project.first_event: project.update(first_event=date) first_event_received.send_robust(project=project, event=event, sender=Project) eventstream.insert( group=group, event=event, is_new=is_new, is_regression=is_regression, is_new_group_environment=is_new_group_environment, primary_hash=hashes[0], # We are choosing to skip consuming the event back # in the eventstream if it's flagged as raw. # This means that we want to publish the event # through the event stream, but we don't care # about post processing and handling the commit. skip_consume=raw, ) # Do this last to ensure signals get emitted even if connection to the # file store breaks temporarily. self.save_attachments(attachments, event) metric_tags = {"from_relay": "_relay_processed" in self._data} metrics.timing("events.latency", received_timestamp - recorded_timestamp, tags=metric_tags) metrics.timing("events.size.data.post_save", event.size, tags=metric_tags) metrics.incr( "events.post_save.normalize.errors", amount=len(self._data.get("errors") or ()), tags=metric_tags, ) return event
def save(self, project, raw=False): from sentry.tasks.post_process import index_event_tags data = self.data project = Project.objects.get_from_cache(id=project) # Check to make sure we're not about to do a bunch of work that's # already been done if we've processed an event with this ID. (This # isn't a perfect solution -- this doesn't handle ``EventMapping`` and # there's a race condition between here and when the event is actually # saved, but it's an improvement. See GH-7677.) try: event = Event.objects.get( project_id=project.id, event_id=data['event_id'], ) except Event.DoesNotExist: pass else: self.logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': data['event_id'], 'project_id': project.id, 'model': Event.__name__, } ) return event # First we pull out our top-level (non-data attr) kwargs event_id = data.pop('event_id') level = data.pop('level') transaction_name = data.pop('transaction', None) culprit = data.pop('culprit', None) logger_name = data.pop('logger', None) server_name = data.pop('server_name', None) site = data.pop('site', None) checksum = data.pop('checksum', None) fingerprint = data.pop('fingerprint', None) platform = data.pop('platform', None) release = data.pop('release', None) dist = data.pop('dist', None) environment = data.pop('environment', None) # unused time_spent = data.pop('time_spent', None) message = data.pop('message', '') if not culprit: if transaction_name: culprit = transaction_name else: culprit = generate_culprit(data, platform=platform) culprit = force_text(culprit) if transaction_name: transaction_name = force_text(transaction_name) recorded_timestamp = data.pop('timestamp') date = datetime.fromtimestamp(recorded_timestamp) date = date.replace(tzinfo=timezone.utc) kwargs = { 'platform': platform, } event = Event( project_id=project.id, event_id=event_id, data=data, time_spent=time_spent, datetime=date, **kwargs ) event._project_cache = project data = event.data.data # convert this to a dict to ensure we're only storing one value per key # as most parts of Sentry dont currently play well with multiple values tags = dict(data.get('tags') or []) tags['level'] = LOG_LEVELS[level] if logger_name: tags['logger'] = logger_name if server_name: tags['server_name'] = server_name if site: tags['site'] = site if environment: tags['environment'] = environment if transaction_name: tags['transaction'] = transaction_name if release: # dont allow a conflicting 'release' tag if 'release' in tags: del tags['release'] release = Release.get_or_create( project=project, version=release, date_added=date, ) tags['sentry:release'] = release.version if dist and release: dist = release.add_dist(dist, date) tags['sentry:dist'] = dist.name else: dist = None event_user = self._get_event_user(project, data) if event_user: # dont allow a conflicting 'user' tag if 'user' in tags: del tags['user'] tags['sentry:user'] = event_user.tag_value # At this point we want to normalize the in_app values in case the # clients did not set this appropriately so far. normalize_in_app(data) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: # plugins should not override user provided tags for key, value in added_tags: tags.setdefault(key, value) for path, iface in six.iteritems(event.interfaces): for k, v in iface.iter_tags(): tags[k] = v # Get rid of ephemeral interface data if iface.ephemeral: data.pop(iface.get_path(), None) # tags are stored as a tuple tags = tags.items() data['tags'] = tags data['fingerprint'] = fingerprint or ['{{ default }}'] # prioritize fingerprint over checksum as its likely the client defaulted # a checksum whereas the fingerprint was explicit if fingerprint: hashes = [md5_from_hash(h) for h in get_hashes_from_fingerprint(event, fingerprint)] elif checksum: if HASH_RE.match(checksum): hashes = [checksum] else: hashes = [md5_from_hash([checksum]), checksum] data['checksum'] = checksum else: hashes = [md5_from_hash(h) for h in get_hashes_for_event(event)] # TODO(dcramer): temp workaround for complexity data['message'] = message event_type = eventtypes.get(data.get('type', 'default'))(data) event_metadata = event_type.get_metadata() # TODO(dcramer): temp workaround for complexity del data['message'] data['type'] = event_type.key data['metadata'] = event_metadata # index components into ``Event.message`` # See GH-3248 if event_type.key != 'default': if 'sentry.interfaces.Message' in data and \ data['sentry.interfaces.Message']['message'] != message: message = u'{} {}'.format( message, data['sentry.interfaces.Message']['message'], ) if not message: message = '' elif not isinstance(message, six.string_types): message = force_text(message) for value in six.itervalues(event_metadata): value_u = force_text(value, errors='replace') if value_u not in message: message = u'{} {}'.format(message, value_u) if culprit and culprit not in message: culprit_u = force_text(culprit, errors='replace') message = u'{} {}'.format(message, culprit_u) message = trim(message.strip(), settings.SENTRY_MAX_MESSAGE_LENGTH) event.message = message kwargs['message'] = message received_timestamp = event.data.get('received') or float(event.datetime.strftime('%s')) group_kwargs = kwargs.copy() group_kwargs.update( { 'culprit': culprit, 'logger': logger_name, 'level': level, 'last_seen': date, 'first_seen': date, 'active_at': date, 'data': { 'last_received': received_timestamp, 'type': event_type.key, # we cache the events metadata on the group to ensure its # accessible in the stream 'metadata': event_metadata, }, } ) if release: group_kwargs['first_release'] = release try: group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **group_kwargs ) except HashDiscarded: event_discarded.send_robust( project=project, sender=EventManager, ) metrics.incr( 'events.discarded', skip_internal=True, tags={ 'organization_id': project.organization_id, 'platform': platform, }, ) raise else: event_saved.send_robust( project=project, event_size=event.size, sender=EventManager, ) event.group = group # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) # When an event was sampled, the canonical source of truth # is the EventMapping table since we aren't going to be writing out an actual # Event row. Otherwise, if the Event isn't being sampled, we can safely # rely on the Event table itself as the source of truth and ignore # EventMapping since it's redundant information. if is_sample: try: with transaction.atomic(using=router.db_for_write(EventMapping)): EventMapping.objects.create(project=project, group=group, event_id=event_id) except IntegrityError: self.logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': EventMapping.__name__, } ) return event environment = Environment.get_or_create( project=project, name=environment, ) group_environment, is_new_group_environment = GroupEnvironment.get_or_create( group_id=group.id, environment_id=environment.id, defaults={ 'first_release_id': release.id if release else None, }, ) if release: ReleaseEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) ReleaseProjectEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) grouprelease = GroupRelease.get_or_create( group=group, release=release, environment=environment, datetime=date, ) counters = [ (tsdb.models.group, group.id), (tsdb.models.project, project.id), ] if release: counters.append((tsdb.models.release, release.id)) tsdb.incr_multi(counters, timestamp=event.datetime, environment_id=environment.id) frequencies = [ # (tsdb.models.frequent_projects_by_organization, { # project.organization_id: { # project.id: 1, # }, # }), # (tsdb.models.frequent_issues_by_project, { # project.id: { # group.id: 1, # }, # }) (tsdb.models.frequent_environments_by_group, { group.id: { environment.id: 1, }, }) ] if release: frequencies.append( (tsdb.models.frequent_releases_by_group, { group.id: { grouprelease.id: 1, }, }) ) tsdb.record_frequency_multi(frequencies, timestamp=event.datetime) UserReport.objects.filter( project=project, event_id=event_id, ).update( group=group, environment=environment, ) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(using=router.db_for_write(Event)): event.save() except IntegrityError: self.logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': Event.__name__, } ) return event index_event_tags.delay( organization_id=project.organization_id, project_id=project.id, group_id=group.id, environment_id=environment.id, event_id=event.id, tags=tags, date_added=event.datetime, ) if event_user: tsdb.record_multi( ( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value, )), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value, )), ), timestamp=event.datetime, environment_id=environment.id, ) if release: if is_new: buffer.incr( ReleaseProject, {'new_groups': 1}, { 'release_id': release.id, 'project_id': project.id, } ) if is_new_group_environment: buffer.incr( ReleaseProjectEnvironment, {'new_issues_count': 1}, { 'project_id': project.id, 'release_id': release.id, 'environment_id': environment.id, } ) safe_execute(Group.objects.add_tags, group, environment, tags, _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) first_event_received.send_robust(project=project, group=group, sender=Project) eventstream.insert( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, is_new_group_environment=is_new_group_environment, primary_hash=hashes[0], # We are choosing to skip consuming the event back # in the eventstream if it's flagged as raw. # This means that we want to publish the event # through the event stream, but we don't care # about post processing and handling the commit. skip_consume=raw, ) metrics.timing( 'events.latency', received_timestamp - recorded_timestamp, tags={ 'project_id': project.id, }, ) return event
def save(self, project, raw=False): from sentry.tasks.post_process import index_event_tags data = self.data project = Project.objects.get_from_cache(id=project) # Check to make sure we're not about to do a bunch of work that's # already been done if we've processed an event with this ID. (This # isn't a perfect solution -- this doesn't handle ``EventMapping`` and # there's a race condition between here and when the event is actually # saved, but it's an improvement. See GH-7677.) try: event = Event.objects.get( project_id=project.id, event_id=data['event_id'], ) except Event.DoesNotExist: pass else: self.logger.info('duplicate.found', exc_info=True, extra={ 'event_uuid': data['event_id'], 'project_id': project.id, 'model': Event.__name__, }) return event # First we pull out our top-level (non-data attr) kwargs event_id = data.pop('event_id') level = data.pop('level') culprit = data.pop('transaction', None) if not culprit: culprit = data.pop('culprit', None) logger_name = data.pop('logger', None) server_name = data.pop('server_name', None) site = data.pop('site', None) checksum = data.pop('checksum', None) fingerprint = data.pop('fingerprint', None) platform = data.pop('platform', None) release = data.pop('release', None) dist = data.pop('dist', None) environment = data.pop('environment', None) # unused time_spent = data.pop('time_spent', None) message = data.pop('message', '') if not culprit: # if we generate an implicit culprit, lets not call it a # transaction transaction_name = None culprit = generate_culprit(data, platform=platform) else: transaction_name = culprit culprit = force_text(culprit) recorded_timestamp = data.pop('timestamp') date = datetime.fromtimestamp(recorded_timestamp) date = date.replace(tzinfo=timezone.utc) kwargs = { 'platform': platform, } event = Event(project_id=project.id, event_id=event_id, data=data, time_spent=time_spent, datetime=date, **kwargs) event._project_cache = project # convert this to a dict to ensure we're only storing one value per key # as most parts of Sentry dont currently play well with multiple values tags = dict(data.get('tags') or []) tags['level'] = LOG_LEVELS[level] if logger_name: tags['logger'] = logger_name if server_name: tags['server_name'] = server_name if site: tags['site'] = site if environment: tags['environment'] = environment if transaction_name: tags['transaction'] = transaction_name if release: # dont allow a conflicting 'release' tag if 'release' in tags: del tags['release'] release = Release.get_or_create( project=project, version=release, date_added=date, ) tags['sentry:release'] = release.version if dist and release: dist = release.add_dist(dist, date) tags['sentry:dist'] = dist.name else: dist = None event_user = self._get_event_user(project, data) if event_user: # dont allow a conflicting 'user' tag if 'user' in tags: del tags['user'] tags['sentry:user'] = event_user.tag_value # At this point we want to normalize the in_app values in case the # clients did not set this appropriately so far. normalize_in_app(data) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: # plugins should not override user provided tags for key, value in added_tags: tags.setdefault(key, value) for path, iface in six.iteritems(event.interfaces): for k, v in iface.iter_tags(): tags[k] = v # Get rid of ephemeral interface data if iface.ephemeral: data.pop(iface.get_path(), None) # tags are stored as a tuple tags = tags.items() data['tags'] = tags data['fingerprint'] = fingerprint or ['{{ default }}'] # prioritize fingerprint over checksum as its likely the client defaulted # a checksum whereas the fingerprint was explicit if fingerprint: hashes = [ md5_from_hash(h) for h in get_hashes_from_fingerprint(event, fingerprint) ] elif checksum: if HASH_RE.match(checksum): hashes = [checksum] else: hashes = [md5_from_hash([checksum]), checksum] data['checksum'] = checksum else: hashes = [md5_from_hash(h) for h in get_hashes_for_event(event)] # TODO(dcramer): temp workaround for complexity data['message'] = message event_type = eventtypes.get(data.get('type', 'default'))(data) event_metadata = event_type.get_metadata() # TODO(dcramer): temp workaround for complexity del data['message'] data['type'] = event_type.key data['metadata'] = event_metadata # index components into ``Event.message`` # See GH-3248 if event_type.key != 'default': if 'sentry.interfaces.Message' in data and \ data['sentry.interfaces.Message']['message'] != message: message = u'{} {}'.format( message, data['sentry.interfaces.Message']['message'], ) if not message: message = '' elif not isinstance(message, six.string_types): message = force_text(message) for value in six.itervalues(event_metadata): value_u = force_text(value, errors='replace') if value_u not in message: message = u'{} {}'.format(message, value_u) if culprit and culprit not in message: culprit_u = force_text(culprit, errors='replace') message = u'{} {}'.format(message, culprit_u) message = trim(message.strip(), settings.SENTRY_MAX_MESSAGE_LENGTH) event.message = message kwargs['message'] = message received_timestamp = event.data.get('received') or float( event.datetime.strftime('%s')) group_kwargs = kwargs.copy() group_kwargs.update({ 'culprit': culprit, 'logger': logger_name, 'level': level, 'last_seen': date, 'first_seen': date, 'active_at': date, 'data': { 'last_received': received_timestamp, 'type': event_type.key, # we cache the events metadata on the group to ensure its # accessible in the stream 'metadata': event_metadata, }, }) if release: group_kwargs['first_release'] = release try: group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **group_kwargs) except HashDiscarded: event_discarded.send_robust( project=project, sender=EventManager, ) metrics.incr( 'events.discarded', skip_internal=True, tags={ 'organization_id': project.organization_id, 'platform': platform, }, ) raise else: event_saved.send_robust( project=project, sender=EventManager, ) event.group = group # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) # When an event was sampled, the canonical source of truth # is the EventMapping table since we aren't going to be writing out an actual # Event row. Otherwise, if the Event isn't being sampled, we can safely # rely on the Event table itself as the source of truth and ignore # EventMapping since it's redundant information. if is_sample: try: with transaction.atomic( using=router.db_for_write(EventMapping)): EventMapping.objects.create(project=project, group=group, event_id=event_id) except IntegrityError: self.logger.info('duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': EventMapping.__name__, }) return event environment = Environment.get_or_create( project=project, name=environment, ) group_environment, is_new_group_environment = GroupEnvironment.get_or_create( group_id=group.id, environment_id=environment.id, defaults={ 'first_release_id': release.id if release else None, }, ) if release: ReleaseEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) ReleaseProjectEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) grouprelease = GroupRelease.get_or_create( group=group, release=release, environment=environment, datetime=date, ) counters = [ (tsdb.models.group, group.id), (tsdb.models.project, project.id), ] if release: counters.append((tsdb.models.release, release.id)) tsdb.incr_multi(counters, timestamp=event.datetime, environment_id=environment.id) frequencies = [ # (tsdb.models.frequent_projects_by_organization, { # project.organization_id: { # project.id: 1, # }, # }), # (tsdb.models.frequent_issues_by_project, { # project.id: { # group.id: 1, # }, # }) (tsdb.models.frequent_environments_by_group, { group.id: { environment.id: 1, }, }) ] if release: frequencies.append((tsdb.models.frequent_releases_by_group, { group.id: { grouprelease.id: 1, }, })) tsdb.record_frequency_multi(frequencies, timestamp=event.datetime) UserReport.objects.filter( project=project, event_id=event_id, ).update( group=group, environment=environment, ) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(using=router.db_for_write(Event)): event.save() except IntegrityError: self.logger.info('duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': Event.__name__, }) return event index_event_tags.delay( organization_id=project.organization_id, project_id=project.id, group_id=group.id, environment_id=environment.id, event_id=event.id, tags=tags, date_added=event.datetime, ) if event_user: tsdb.record_multi( ( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value, )), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value, )), ), timestamp=event.datetime, environment_id=environment.id, ) if release: if is_new: buffer.incr(ReleaseProject, {'new_groups': 1}, { 'release_id': release.id, 'project_id': project.id, }) if is_new_group_environment: buffer.incr(ReleaseProjectEnvironment, {'new_issues_count': 1}, { 'project_id': project.id, 'release_id': release.id, 'environment_id': environment.id, }) safe_execute(Group.objects.add_tags, group, environment, tags, _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) first_event_received.send(project=project, group=group, sender=Project) post_process_group.delay( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, is_new_group_environment=is_new_group_environment, primary_hash=hashes[0], ) else: self.logger.info('post_process.skip.raw_event', extra={'event_id': event.id}) metrics.timing( 'events.latency', received_timestamp - recorded_timestamp, tags={ 'project_id': project.id, }, ) return event
def save(self, project, raw=False): project = Project.objects.get_from_cache(id=project) data = self.data.copy() # First we pull out our top-level (non-data attr) kwargs event_id = data.pop('event_id') message = data.pop('message') level = data.pop('level') culprit = data.pop('culprit', None) time_spent = data.pop('time_spent', None) logger_name = data.pop('logger', None) server_name = data.pop('server_name', None) site = data.pop('site', None) checksum = data.pop('checksum', None) fingerprint = data.pop('fingerprint', None) platform = data.pop('platform', None) release = data.pop('release', None) environment = data.pop('environment', None) if not culprit: culprit = generate_culprit(data) date = datetime.fromtimestamp(data.pop('timestamp')) date = date.replace(tzinfo=timezone.utc) kwargs = { 'message': message, 'platform': platform, } event = Event(project_id=project.id, event_id=event_id, data=data, time_spent=time_spent, datetime=date, **kwargs) tags = data.get('tags') or [] tags.append(('level', LOG_LEVELS[level])) if logger_name: tags.append(('logger', logger_name)) if server_name: tags.append(('server_name', server_name)) if site: tags.append(('site', site)) if release: # TODO(dcramer): we should ensure we create Release objects tags.append(('sentry:release', release)) if environment: tags.append(('environment', environment)) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: tags.extend(added_tags) event_user = self._get_event_user(project, data) if event_user: tags.append(('sentry:user', event_user.tag_value)) # XXX(dcramer): we're relying on mutation of the data object to ensure # this propagates into Event data['tags'] = tags data['fingerprint'] = fingerprint or ['{{ default }}'] # prioritize fingerprint over checksum as its likely the client defaulted # a checksum whereas the fingerprint was explicit if fingerprint: hashes = map(md5_from_hash, get_hashes_from_fingerprint(event, fingerprint)) elif checksum: hashes = [checksum] else: hashes = map(md5_from_hash, get_hashes_for_event(event)) group_kwargs = kwargs.copy() group_kwargs.update({ 'culprit': culprit, 'logger': logger_name, 'level': level, 'last_seen': date, 'first_seen': date, 'time_spent_total': time_spent or 0, 'time_spent_count': time_spent and 1 or 0, }) if release: release = Release.get_or_create( project=project, version=release, date_added=date, ) group_kwargs['first_release'] = release group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **group_kwargs) event.group = group # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) try: with transaction.atomic(using=router.db_for_write(EventMapping)): EventMapping.objects.create(project=project, group=group, event_id=event_id) except IntegrityError: self.logger.info('Duplicate EventMapping found for event_id=%s', event_id, exc_info=True) return event UserReport.objects.filter( project=project, event_id=event_id, ).update(group=group) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(using=router.db_for_write(Event)): event.save() except IntegrityError: self.logger.info('Duplicate Event found for event_id=%s', event_id, exc_info=True) return event if event_user: tsdb.record_multi(( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value, )), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value, )), ), timestamp=event.datetime) if is_new and release: buffer.incr(Release, {'new_groups': 1}, { 'id': release.id, }) safe_execute(Group.objects.add_tags, group, tags, _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) post_process_group.delay( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, ) else: self.logger.info( 'Raw event passed; skipping post process for event_id=%s', event_id) # TODO: move this to the queue if is_regression and not raw: regression_signal.send_robust(sender=Group, instance=group) return event
def save(self, project, raw=False): from sentry.tasks.post_process import index_event_tags project = Project.objects.get_from_cache(id=project) data = self.data.copy() # First we pull out our top-level (non-data attr) kwargs event_id = data.pop("event_id") message = data.pop("message") level = data.pop("level") culprit = data.pop("culprit", None) logger_name = data.pop("logger", None) server_name = data.pop("server_name", None) site = data.pop("site", None) checksum = data.pop("checksum", None) fingerprint = data.pop("fingerprint", None) platform = data.pop("platform", None) release = data.pop("release", None) environment = data.pop("environment", None) # unused time_spent = data.pop("time_spent", None) if not culprit: culprit = generate_culprit(data, platform=platform) date = datetime.fromtimestamp(data.pop("timestamp")) date = date.replace(tzinfo=timezone.utc) kwargs = {"message": message, "platform": platform} event = Event( project_id=project.id, event_id=event_id, data=data, time_spent=time_spent, datetime=date, **kwargs ) tags = data.get("tags") or [] tags.append(("level", LOG_LEVELS[level])) if logger_name: tags.append(("logger", logger_name)) if server_name: tags.append(("server_name", server_name)) if site: tags.append(("site", site)) if release: # TODO(dcramer): we should ensure we create Release objects tags.append(("sentry:release", release)) if environment: tags.append(("environment", environment)) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: tags.extend(added_tags) event_user = self._get_event_user(project, data) if event_user: tags.append(("sentry:user", event_user.tag_value)) # XXX(dcramer): we're relying on mutation of the data object to ensure # this propagates into Event data["tags"] = tags data["fingerprint"] = fingerprint or ["{{ default }}"] # Get rid of ephemeral interface data for interface_class, _ in iter_interfaces(): interface = interface_class() if interface.ephemeral: data.pop(interface.get_path(), None) # prioritize fingerprint over checksum as its likely the client defaulted # a checksum whereas the fingerprint was explicit if fingerprint: hashes = map(md5_from_hash, get_hashes_from_fingerprint(event, fingerprint)) elif checksum: hashes = [checksum] else: hashes = map(md5_from_hash, get_hashes_for_event(event)) # TODO(dcramer): temp workaround for complexity data["message"] = message event_type = eventtypes.get(data.get("type", "default"))(data) group_kwargs = kwargs.copy() group_kwargs.update( { "culprit": culprit, "logger": logger_name, "level": level, "last_seen": date, "first_seen": date, "data": { "last_received": event.data.get("received") or float(event.datetime.strftime("%s")), "type": event_type.key, # we cache the events metadata on the group to ensure its # accessible in the stream "metadata": event_type.get_metadata(), }, } ) # TODO(dcramer): temp workaround for complexity del data["message"] if release: release = Release.get_or_create(project=project, version=release, date_added=date) group_kwargs["first_release"] = release group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **group_kwargs ) event.group = group # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) try: with transaction.atomic(using=router.db_for_write(EventMapping)): EventMapping.objects.create(project=project, group=group, event_id=event_id) except IntegrityError: self.logger.info("Duplicate EventMapping found for event_id=%s", event_id, exc_info=True) return event UserReport.objects.filter(project=project, event_id=event_id).update(group=group) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(using=router.db_for_write(Event)): event.save() except IntegrityError: self.logger.info("Duplicate Event found for event_id=%s", event_id, exc_info=True) return event index_event_tags.delay(project_id=project.id, event_id=event.id, tags=tags) if event_user: tsdb.record_multi( ( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value,)), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value,)), ), timestamp=event.datetime, ) if is_new and release: buffer.incr(Release, {"new_groups": 1}, {"id": release.id}) safe_execute(Group.objects.add_tags, group, tags, _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) first_event_received.send(project=project, group=group, sender=Project) post_process_group.delay( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression ) else: self.logger.info("Raw event passed; skipping post process for event_id=%s", event_id) # TODO: move this to the queue if is_regression and not raw: regression_signal.send_robust(sender=Group, instance=group) return event
def get_event_file_committers(project, event, frame_limit=25): # populate event data Event.objects.bind_nodes([event], 'data') group = Group.objects.get(id=event.group_id) first_release_version = group.get_first_release() if not first_release_version: raise Release.DoesNotExist releases = Release.get_closest_releases(project, first_release_version) if not releases: raise Release.DoesNotExist commits = _get_commits(releases) if not commits: raise Commit.DoesNotExist frames = _get_frame_paths(event) app_frames = [frame for frame in frames if frame['in_app']][-frame_limit:] if not app_frames: app_frames = [frame for frame in frames][-frame_limit:] # TODO(maxbittker) return this set instead of annotated frames # XXX(dcramer): frames may not define a filepath. For example, in Java its common # to only have a module name/path path_set = { f for f in (frame.get('filename') or frame.get('abs_path') for frame in app_frames) if f } file_changes = [] if path_set: file_changes = _get_commit_file_changes(commits, path_set) commit_path_matches = { path: _match_commits_path(file_changes, path) for path in path_set } annotated_frames = [{ 'frame': frame, 'commits': commit_path_matches.get( frame.get('filename') or frame.get('abs_path')) or [] } for frame in app_frames] relevant_commits = list({ match for match in commit_path_matches for match in commit_path_matches[match] }) committers = _get_committers(annotated_frames, relevant_commits) metrics.incr('feature.owners.has-committers', instance='hit' if committers else 'miss') return committers
def merge_to(from_org, to_org): from sentry.models import ( ApiKey, AuditLogEntry, Commit, OrganizationMember, OrganizationMemberTeam, Project, Release, ReleaseCommit, ReleaseEnvironment, ReleaseFile, Repository, Team ) for from_member in OrganizationMember.objects.filter(organization=from_org): try: to_member = OrganizationMember.objects.get( organization=to_org, user=from_member.user, ) except OrganizationMember.DoesNotExist: from_member.update(organization=to_org) to_member = from_member else: qs = OrganizationMemberTeam.objects.filter( organizationmember=from_member, is_active=True, ).select_related() for omt in qs: OrganizationMemberTeam.objects.create_or_update( organizationmember=to_member, team=omt.team, defaults={ 'is_active': True, }, ) for team in Team.objects.filter(organization=from_org): try: with transaction.atomic(): team.update(organization=to_org) except IntegrityError: slugify_instance(team, team.name, organization=to_org) team.update( organization=to_org, slug=team.slug, ) for project in Project.objects.filter(organization=from_org): try: with transaction.atomic(): project.update(organization=to_org) except IntegrityError: slugify_instance(project, project.name, organization=to_org) project.update( organization=to_org, slug=project.slug, ) # TODO(jess): update this when adding unique constraint # on version, organization for releases for release in Release.objects.filter(organization=from_org): try: to_release = Release.objects.get( version=release.version, organization=to_org ) except Release.DoesNotExist: Release.objects.filter( id=release.id ).update(organization=to_org) else: Release.merge(to_release, [release]) for model in (ApiKey, AuditLogEntry, ReleaseFile): model.objects.filter( organization=from_org, ).update(organization=to_org) for model in (Commit, ReleaseCommit, ReleaseEnvironment, Repository): model.objects.filter( organization_id=from_org.id, ).update(organization_id=to_org.id)
def save(self, project, raw=False): from sentry.tasks.post_process import index_event_tags project = Project.objects.get_from_cache(id=project) data = self.data.copy() # First we pull out our top-level (non-data attr) kwargs event_id = data.pop('event_id') level = data.pop('level') culprit = data.pop('transaction', None) if not culprit: culprit = data.pop('culprit', None) logger_name = data.pop('logger', None) server_name = data.pop('server_name', None) site = data.pop('site', None) checksum = data.pop('checksum', None) fingerprint = data.pop('fingerprint', None) platform = data.pop('platform', None) release = data.pop('release', None) dist = data.pop('dist', None) environment = data.pop('environment', None) # unused time_spent = data.pop('time_spent', None) message = data.pop('message', '') if not culprit: # if we generate an implicit culprit, lets not call it a # transaction transaction_name = None culprit = generate_culprit(data, platform=platform) else: transaction_name = culprit date = datetime.fromtimestamp(data.pop('timestamp')) date = date.replace(tzinfo=timezone.utc) kwargs = { 'platform': platform, } event = Event( project_id=project.id, event_id=event_id, data=data, time_spent=time_spent, datetime=date, **kwargs ) event._project_cache = project # convert this to a dict to ensure we're only storing one value per key # as most parts of Sentry dont currently play well with multiple values tags = dict(data.get('tags') or []) tags['level'] = LOG_LEVELS[level] if logger_name: tags['logger'] = logger_name if server_name: tags['server_name'] = server_name if site: tags['site'] = site if environment: tags['environment'] = environment if transaction_name: tags['transaction'] = transaction_name if release: # dont allow a conflicting 'release' tag if 'release' in tags: del tags['release'] release = Release.get_or_create( project=project, version=release, date_added=date, ) tags['sentry:release'] = release.version if dist and release: dist = release.add_dist(dist, date) tags['sentry:dist'] = dist.name else: dist = None event_user = self._get_event_user(project, data) if event_user: # dont allow a conflicting 'user' tag if 'user' in tags: del tags['user'] tags['sentry:user'] = event_user.tag_value # At this point we want to normalize the in_app values in case the # clients did not set this appropriately so far. normalize_in_app(data) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: # plugins should not override user provided tags for key, value in added_tags: tags.setdefault(key, value) # tags are stored as a tuple tags = tags.items() # XXX(dcramer): we're relying on mutation of the data object to ensure # this propagates into Event data['tags'] = tags data['fingerprint'] = fingerprint or ['{{ default }}'] for path, iface in six.iteritems(event.interfaces): data['tags'].extend(iface.iter_tags()) # Get rid of ephemeral interface data if iface.ephemeral: data.pop(iface.get_path(), None) # prioritize fingerprint over checksum as its likely the client defaulted # a checksum whereas the fingerprint was explicit if fingerprint: hashes = [md5_from_hash(h) for h in get_hashes_from_fingerprint(event, fingerprint)] elif checksum: hashes = [checksum] data['checksum'] = checksum else: hashes = [md5_from_hash(h) for h in get_hashes_for_event(event)] # TODO(dcramer): temp workaround for complexity data['message'] = message event_type = eventtypes.get(data.get('type', 'default'))(data) event_metadata = event_type.get_metadata() # TODO(dcramer): temp workaround for complexity del data['message'] data['type'] = event_type.key data['metadata'] = event_metadata # index components into ``Event.message`` # See GH-3248 if event_type.key != 'default': if 'sentry.interfaces.Message' in data and \ data['sentry.interfaces.Message']['message'] != message: message = u'{} {}'.format( message, data['sentry.interfaces.Message']['message'], ) if not message: message = '' elif not isinstance(message, six.string_types): message = force_text(message) for value in six.itervalues(event_metadata): value_u = force_text(value, errors='replace') if value_u not in message: message = u'{} {}'.format(message, value_u) if culprit and culprit not in message: culprit_u = force_text(culprit, errors='replace') message = u'{} {}'.format(message, culprit_u) message = trim(message.strip(), settings.SENTRY_MAX_MESSAGE_LENGTH) event.message = message kwargs['message'] = message group_kwargs = kwargs.copy() group_kwargs.update( { 'culprit': culprit, 'logger': logger_name, 'level': level, 'last_seen': date, 'first_seen': date, 'active_at': date, 'data': { 'last_received': event.data.get('received') or float(event.datetime.strftime('%s')), 'type': event_type.key, # we cache the events metadata on the group to ensure its # accessible in the stream 'metadata': event_metadata, }, } ) if release: group_kwargs['first_release'] = release group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **group_kwargs ) event.group = group # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) try: with transaction.atomic(using=router.db_for_write(EventMapping)): EventMapping.objects.create(project=project, group=group, event_id=event_id) except IntegrityError: self.logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': EventMapping.__name__, } ) return event environment = Environment.get_or_create( project=project, name=environment, ) if release: ReleaseEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) grouprelease = GroupRelease.get_or_create( group=group, release=release, environment=environment, datetime=date, ) counters = [ (tsdb.models.group, group.id), (tsdb.models.project, project.id), ] if release: counters.append((tsdb.models.release, release.id)) tsdb.incr_multi(counters, timestamp=event.datetime) frequencies = [ # (tsdb.models.frequent_projects_by_organization, { # project.organization_id: { # project.id: 1, # }, # }), # (tsdb.models.frequent_issues_by_project, { # project.id: { # group.id: 1, # }, # }) (tsdb.models.frequent_environments_by_group, { group.id: { environment.id: 1, }, }) ] if release: frequencies.append( (tsdb.models.frequent_releases_by_group, { group.id: { grouprelease.id: 1, }, }) ) tsdb.record_frequency_multi(frequencies, timestamp=event.datetime) UserReport.objects.filter( project=project, event_id=event_id, ).update(group=group) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(using=router.db_for_write(Event)): event.save() except IntegrityError: self.logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': Event.__name__, } ) return event index_event_tags.delay( organization_id=project.organization_id, project_id=project.id, group_id=group.id, event_id=event.id, tags=tags, ) if event_user: tsdb.record_multi( ( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value, )), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value, )), ), timestamp=event.datetime ) if is_new and release: buffer.incr( ReleaseProject, {'new_groups': 1}, { 'release_id': release.id, 'project_id': project.id, } ) safe_execute(Group.objects.add_tags, group, tags, _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) first_event_received.send(project=project, group=group, sender=Project) post_process_group.delay( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, ) else: self.logger.info('post_process.skip.raw_event', extra={'event_id': event.id}) # TODO: move this to the queue if is_regression and not raw: regression_signal.send_robust(sender=Group, instance=group) return event
def create_message_event(template, parameters, environment, release): i = next(sequence) event_id = uuid.UUID( fields=(i, 0x0, 0x1000, 0x80, 0x80, 0x808080808080, ), ).hex tags = [['color', next(tag_values)]] if environment: tags.append(['environment', environment]) if release: tags.append(['sentry:release', release]) event = Event.objects.create( project_id=project.id, group_id=source.id, event_id=event_id, message='%s' % (id, ), datetime=now + shift(i), data={ 'environment': environment, 'type': 'default', 'metadata': { 'title': template % parameters, }, 'logentry': { 'message': template, 'params': parameters, 'formatted': template % parameters, }, 'user': next(user_values), 'tags': tags, }, ) with self.tasks(): Group.objects.add_tags( source, Environment.objects.get( organization_id=project.organization_id, name=environment ), tags=event.get_tags(), ) EventMapping.objects.create( project_id=project.id, group_id=source.id, event_id=event_id, date_added=event.datetime, ) UserReport.objects.create( project_id=project.id, group_id=source.id, event_id=event_id, name='Log Hat', email='*****@*****.**', comments='Quack', ) if release: Release.get_or_create( project=project, version=event.get_tag('sentry:release'), date_added=event.datetime, ) features.record([event]) return event
def post(self, request, project): """ Create a New Release ```````````````````` Create a new release and/or associate a project with a release. Release versions that are the same across multiple projects within an Organization will be treated as the same release in Sentry. Releases are used by Sentry to improve its error reporting abilities by correlating first seen events with the release that might have introduced the problem. Releases are also necessary for sourcemaps and other debug features that require manual upload for functioning well. :pparam string organization_slug: the slug of the organization the release belongs to. :pparam string project_slug: the slug of the project to create a release for. :param string version: a version identifier for this release. Can be a version number, a commit hash etc. :param string ref: an optional commit reference. This is useful if a tagged version has been provided. :param url url: a URL that points to the release. This can be the path to an online interface to the sourcecode for instance. :param datetime dateStarted: an optional date that indicates when the release process started. :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :auth: required """ serializer = ReleaseSerializer(data=request.DATA) if serializer.is_valid(): result = serializer.object # release creation is idempotent to simplify user # experiences release = Release.objects.filter( organization_id=project.organization_id, version=result['version'], projects=project).first() created = False if release: was_released = bool(release.date_released) else: release = Release.objects.filter( organization_id=project.organization_id, version=result['version'], ).first() if not release: lock_key = Release.get_lock_key(project.organization_id, result['version']) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release, created = Release.objects.get( version=result['version'], organization_id=project.organization_id), False except Release.DoesNotExist: release, created = Release.objects.create( organization_id=project.organization_id, version=result['version'], ref=result.get('ref'), url=result.get('url'), owner=result.get('owner'), date_started=result.get('dateStarted'), date_released=result.get('dateReleased'), ), True was_released = False try: with transaction.atomic(): ReleaseProject.objects.create(project=project, release=release) created = True except IntegrityError: pass commit_list = result.get('commits') if commit_list: hook = ReleaseHook(project) # TODO(dcramer): handle errors with release payloads hook.set_commits(release.version, commit_list) if (not was_released and release.date_released): activity = Activity.objects.create( type=Activity.RELEASE, project=project, ident=result['version'], data={'version': result['version']}, datetime=release.date_released, ) activity.send_notification() if not created: # This is the closest status code that makes sense, and we want # a unique 2xx response code so people can understand when # behavior differs. # 208 Already Reported (WebDAV; RFC 5842) status = 208 else: status = 201 return Response(serialize(release, request.user), status=status) return Response(serializer.errors, status=400)
def merge_to(from_org, to_org): from sentry.models import ( ApiKey, AuditLogEntry, AuthProvider, Commit, OrganizationAvatar, OrganizationIntegration, OrganizationMember, OrganizationMemberTeam, Project, Release, ReleaseCommit, ReleaseEnvironment, ReleaseFile, ReleaseHeadCommit, Repository, Team, Environment, ) for from_member in OrganizationMember.objects.filter( organization=from_org, user__isnull=False ): logger = logging.getLogger('sentry.merge') try: to_member = OrganizationMember.objects.get( organization=to_org, user=from_member.user, ) except OrganizationMember.DoesNotExist: from_member.update(organization=to_org) to_member = from_member else: qs = OrganizationMemberTeam.objects.filter( organizationmember=from_member, is_active=True, ).select_related() for omt in qs: OrganizationMemberTeam.objects.create_or_update( organizationmember=to_member, team=omt.team, defaults={ 'is_active': True, }, ) logger.info('user.migrate', extra={ 'instance_id': from_member.id, 'new_member_id': to_member.id, 'from_organization_id': from_org.id, 'to_organization_id': to_org.id, }) for from_team in Team.objects.filter(organization=from_org): try: with transaction.atomic(): from_team.update(organization=to_org) except IntegrityError: slugify_instance(from_team, from_team.name, organization=to_org) from_team.update( organization=to_org, slug=from_team.slug, ) logger.info('team.migrate', extra={ 'instance_id': from_team.id, 'new_slug': from_team.slug, 'from_organization_id': from_org.id, 'to_organization_id': to_org.id, }) for from_project in Project.objects.filter(organization=from_org): try: with transaction.atomic(): from_project.update(organization=to_org) except IntegrityError: slugify_instance( from_project, from_project.name, organization=to_org, reserved=RESERVED_PROJECT_SLUGS) from_project.update( organization=to_org, slug=from_project.slug, ) logger.info('project.migrate', extra={ 'instance_id': from_project.id, 'new_slug': from_project.slug, 'from_organization_id': from_org.id, 'to_organization_id': to_org.id, }) # TODO(jess): update this when adding unique constraint # on version, organization for releases for from_release in Release.objects.filter(organization=from_org): try: to_release = Release.objects.get(version=from_release.version, organization=to_org) except Release.DoesNotExist: Release.objects.filter(id=from_release.id).update(organization=to_org) else: Release.merge(to_release, [from_release]) logger.info('release.migrate', extra={ 'instance_id': from_release.id, 'from_organization_id': from_org.id, 'to_organization_id': to_org.id, }) def do_update(queryset, params): model_name = queryset.model.__name__.lower() try: with transaction.atomic(): queryset.update(**params) except IntegrityError: for instance in queryset: try: with transaction.atomic(): instance.update(**params) except IntegrityError: logger.info('{}.migrate-skipped'.format(model_name), extra={ 'from_organization_id': from_org.id, 'to_organization_id': to_org.id, }) else: logger.info('{}.migrate'.format(model_name), extra={ 'instance_id': instance.id, 'from_organization_id': from_org.id, 'to_organization_id': to_org.id, }) else: logger.info('{}.migrate'.format(model_name), extra={ 'from_organization_id': from_org.id, 'to_organization_id': to_org.id, }) INST_MODEL_LIST = ( AuthProvider, ApiKey, AuditLogEntry, OrganizationAvatar, OrganizationIntegration, ReleaseEnvironment, ReleaseFile, ) ATTR_MODEL_LIST = ( Commit, ReleaseCommit, ReleaseHeadCommit, Repository, Environment, ) for model in INST_MODEL_LIST: queryset = model.objects.filter( organization=from_org, ) do_update(queryset, {'organization': to_org}) for model in ATTR_MODEL_LIST: queryset = model.objects.filter( organization_id=from_org.id, ) do_update(queryset, {'organization_id': to_org.id})
def test_simple(self): org = self.create_organization() commit = Commit.objects.create(organization_id=org.id, repository_id=5) commit2 = Commit.objects.create(organization_id=org.id, repository_id=6) # merge to project = self.create_project(organization=org, name='foo') environment = Environment.get_or_create(project=project, name='env1') release = Release.objects.create(version='abcdabc', organization=org) release.add_project(project) release_commit = ReleaseCommit.objects.create( organization_id=org.id, release=release, commit=commit, order=1 ) release_environment = ReleaseEnvironment.objects.create( organization_id=org.id, project_id=project.id, release_id=release.id, environment_id=environment.id ) release_project_environment = ReleaseProjectEnvironment.objects.create( release_id=release.id, project_id=project.id, environment_id=environment.id ) group_release = GroupRelease.objects.create( project_id=project.id, release_id=release.id, group_id=1 ) group = self.create_group(project=project, first_release=release) group_resolution = GroupResolution.objects.create(group=group, release=release) # merge from #1 project2 = self.create_project(organization=org, name='bar') environment2 = Environment.get_or_create(project=project2, name='env2') release2 = Release.objects.create(version='bbbbbbb', organization=org) release2.add_project(project2) release_commit2 = ReleaseCommit.objects.create( organization_id=org.id, release=release2, commit=commit, order=2 ) release_environment2 = ReleaseEnvironment.objects.create( organization_id=org.id, project_id=project2.id, release_id=release2.id, environment_id=environment2.id, ) release_project_environment2 = ReleaseProjectEnvironment.objects.create( release_id=release2.id, project_id=project2.id, environment_id=environment2.id ) group_release2 = GroupRelease.objects.create( project_id=project2.id, release_id=release2.id, group_id=2 ) group2 = self.create_group(project=project2, first_release=release2) group_resolution2 = GroupResolution.objects.create(group=group2, release=release2) # merge from #2 project3 = self.create_project(organization=org, name='baz') environment3 = Environment.get_or_create(project=project3, name='env3') release3 = Release.objects.create(version='cccccc', organization=org) release3.add_project(project3) release_commit3 = ReleaseCommit.objects.create( organization_id=org.id, release=release2, commit=commit2, order=3 ) release_environment3 = ReleaseEnvironment.objects.create( organization_id=org.id, project_id=project3.id, release_id=release3.id, environment_id=environment3.id, ) release_project_environment3 = ReleaseProjectEnvironment.objects.create( release_id=release3.id, project_id=project3.id, environment_id=environment3.id ) group_release3 = GroupRelease.objects.create( project_id=project3.id, release_id=release3.id, group_id=3 ) group3 = self.create_group(project=project3, first_release=release3) group_resolution3 = GroupResolution.objects.create(group=group3, release=release3) Release.merge(release, [release2, release3]) # ReleaseCommit.release assert ReleaseCommit.objects.get(id=release_commit.id).release == release # should not exist because they referenced the same commit assert not ReleaseCommit.objects.filter(id=release_commit2.id).exists() assert ReleaseCommit.objects.get(id=release_commit3.id).release == release # ReleaseEnvironment.release_id assert ReleaseEnvironment.objects.get(id=release_environment.id).release_id == release.id assert ReleaseEnvironment.objects.get(id=release_environment2.id).release_id == release.id assert ReleaseEnvironment.objects.get(id=release_environment3.id).release_id == release.id # ReleaseProject.release assert release.projects.count() == 3 assert ReleaseProject.objects.filter(release=release, project=project).exists() assert ReleaseProject.objects.filter(release=release, project=project2).exists() assert ReleaseProject.objects.filter(release=release, project=project3).exists() # ReleaseProjectEnvironment.release assert ReleaseProjectEnvironment.objects.get( id=release_project_environment.id).release_id == release.id assert ReleaseProjectEnvironment.objects.get( id=release_project_environment2.id).release_id == release.id assert ReleaseProjectEnvironment.objects.get( id=release_project_environment3.id).release_id == release.id # GroupRelease.release_id assert GroupRelease.objects.get(id=group_release.id).release_id == release.id assert GroupRelease.objects.get(id=group_release2.id).release_id == release.id assert GroupRelease.objects.get(id=group_release3.id).release_id == release.id # GroupResolution.release assert GroupResolution.objects.get(id=group_resolution.id).release == release assert GroupResolution.objects.get(id=group_resolution2.id).release == release assert GroupResolution.objects.get(id=group_resolution3.id).release == release # Group.first_release assert Group.objects.get(id=group.id).first_release == release assert Group.objects.get(id=group2.id).first_release == release assert Group.objects.get(id=group3.id).first_release == release # Releases are gone assert Release.objects.filter(id=release.id).exists() assert not Release.objects.filter(id=release2.id).exists() assert not Release.objects.filter(id=release3.id).exists()
def save(self, project, raw=False): project = Project.objects.get_from_cache(id=project) data = self.data.copy() # First we pull out our top-level (non-data attr) kwargs event_id = data.pop('event_id') message = data.pop('message') level = data.pop('level') culprit = data.pop('culprit', None) time_spent = data.pop('time_spent', None) logger_name = data.pop('logger', None) server_name = data.pop('server_name', None) site = data.pop('site', None) checksum = data.pop('checksum', None) fingerprint = data.pop('fingerprint', None) platform = data.pop('platform', None) release = data.pop('release', None) environment = data.pop('environment', None) if not culprit: culprit = generate_culprit(data) date = datetime.fromtimestamp(data.pop('timestamp')) date = date.replace(tzinfo=timezone.utc) kwargs = { 'message': message, 'platform': platform, } event = Event( project=project, event_id=event_id, data=data, time_spent=time_spent, datetime=date, **kwargs ) tags = data.get('tags') or [] tags.append(('level', LOG_LEVELS[level])) if logger_name: tags.append(('logger', logger_name)) if server_name: tags.append(('server_name', server_name)) if site: tags.append(('site', site)) if release: # TODO(dcramer): we should ensure we create Release objects tags.append(('sentry:release', release)) if environment: tags.append(('environment', environment)) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: tags.extend(added_tags) event_user = self._get_event_user(project, data) if event_user: tags.append(('sentry:user', event_user.tag_value)) # XXX(dcramer): we're relying on mutation of the data object to ensure # this propagates into Event data['tags'] = tags data['fingerprint'] = fingerprint or '{{ default }}' # prioritize fingerprint over checksum as its likely the client defaulted # a checksum whereas the fingerprint was explicit if fingerprint: hashes = map(md5_from_hash, get_hashes_from_fingerprint(event, fingerprint)) elif checksum: hashes = [checksum] else: hashes = map(md5_from_hash, get_hashes_for_event(event)) group_kwargs = kwargs.copy() group_kwargs.update({ 'culprit': culprit, 'logger': logger_name, 'level': level, 'last_seen': date, 'first_seen': date, 'time_spent_total': time_spent or 0, 'time_spent_count': time_spent and 1 or 0, }) if release: release = Release.get_or_create( project=project, version=release, date_added=date, ) group_kwargs['first_release'] = release Activity.objects.create( type=Activity.RELEASE, project=project, ident=release, data={'version': release}, datetime=date, ) group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **group_kwargs ) event.group = group event.group_id = group.id # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) try: with transaction.atomic(): EventMapping.objects.create( project=project, group=group, event_id=event_id) except IntegrityError: self.logger.info('Duplicate EventMapping found for event_id=%s', event_id) return event UserReport.objects.filter( project=project, event_id=event_id, ).update(group=group) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(): event.save() except IntegrityError: self.logger.info('Duplicate Event found for event_id=%s', event_id) return event if event_user: tsdb.record_multi(( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value,)), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value,)), ), timestamp=event.datetime) if is_new and release: buffer.incr(Release, {'new_groups': 1}, { 'id': release.id, }) safe_execute(Group.objects.add_tags, group, tags, _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) post_process_group.delay( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, ) else: self.logger.info('Raw event passed; skipping post process for event_id=%s', event_id) index_event.delay(event) # TODO: move this to the queue if is_regression and not raw: regression_signal.send_robust(sender=Group, instance=group) return event
def save(self, project, raw=False): from sentry.tasks.post_process import index_event_tags project = Project.objects.get_from_cache(id=project) data = self.data.copy() # First we pull out our top-level (non-data attr) kwargs event_id = data.pop('event_id') level = data.pop('level') culprit = data.pop('culprit', None) logger_name = data.pop('logger', None) server_name = data.pop('server_name', None) site = data.pop('site', None) checksum = data.pop('checksum', None) fingerprint = data.pop('fingerprint', None) platform = data.pop('platform', None) release = data.pop('release', None) environment = data.pop('environment', None) # unused time_spent = data.pop('time_spent', None) message = data.pop('message', '') if not culprit: culprit = generate_culprit(data, platform=platform) date = datetime.fromtimestamp(data.pop('timestamp')) date = date.replace(tzinfo=timezone.utc) kwargs = { 'platform': platform, } event = Event( project_id=project.id, event_id=event_id, data=data, time_spent=time_spent, datetime=date, **kwargs ) tags = data.get('tags') or [] tags.append(('level', LOG_LEVELS[level])) if logger_name: tags.append(('logger', logger_name)) if server_name: tags.append(('server_name', server_name)) if site: tags.append(('site', site)) if release: # TODO(dcramer): we should ensure we create Release objects tags.append(('sentry:release', release)) if environment: tags.append(('environment', environment)) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: tags.extend(added_tags) event_user = self._get_event_user(project, data) if event_user: tags.append(('sentry:user', event_user.tag_value)) # XXX(dcramer): we're relying on mutation of the data object to ensure # this propagates into Event data['tags'] = tags data['fingerprint'] = fingerprint or ['{{ default }}'] for path, iface in event.interfaces.iteritems(): data['tags'].extend(iface.iter_tags()) # Get rid of ephemeral interface data if iface.ephemeral: data.pop(iface.get_path(), None) # prioritize fingerprint over checksum as its likely the client defaulted # a checksum whereas the fingerprint was explicit if fingerprint: hashes = map(md5_from_hash, get_hashes_from_fingerprint(event, fingerprint)) elif checksum: hashes = [checksum] else: hashes = map(md5_from_hash, get_hashes_for_event(event)) # TODO(dcramer): temp workaround for complexity data['message'] = message event_type = eventtypes.get(data.get('type', 'default'))(data) event_metadata = event_type.get_metadata() # TODO(dcramer): temp workaround for complexity del data['message'] data['type'] = event_type.key data['metadata'] = event_metadata # index components into ``Event.message`` # See GH-3248 if event_type.key != 'default': if 'sentry.interfaces.Message' in data and \ data['sentry.interfaces.Message']['message'] != message: message = u'{} {}'.format( message, data['sentry.interfaces.Message']['message'], ) if not message: message = '' elif not isinstance(message, basestring): message = force_text(message) for value in event_metadata.itervalues(): value_u = force_text(value, errors='replace') if value_u not in message: message = u'{} {}'.format(message, value_u) message = trim(message.strip(), settings.SENTRY_MAX_MESSAGE_LENGTH) event.message = message kwargs['message'] = message group_kwargs = kwargs.copy() group_kwargs.update({ 'culprit': culprit, 'logger': logger_name, 'level': level, 'last_seen': date, 'first_seen': date, 'data': { 'last_received': event.data.get('received') or float(event.datetime.strftime('%s')), 'type': event_type.key, # we cache the events metadata on the group to ensure its # accessible in the stream 'metadata': event_metadata, }, }) if release: release = Release.get_or_create( project=project, version=release, date_added=date, ) group_kwargs['first_release'] = release group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **group_kwargs ) event.group = group # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) try: with transaction.atomic(using=router.db_for_write(EventMapping)): EventMapping.objects.create( project=project, group=group, event_id=event_id) except IntegrityError: self.logger.info('Duplicate EventMapping found for event_id=%s', event_id, exc_info=True) return event if release: grouprelease = GroupRelease.get_or_create( group=group, release=release, environment=environment, datetime=date, ) tsdb.incr_multi([ (tsdb.models.group, group.id), (tsdb.models.project, project.id), ], timestamp=event.datetime) frequencies = [ (tsdb.models.frequent_projects_by_organization, { project.organization_id: { project.id: 1, }, }), (tsdb.models.frequent_issues_by_project, { project.id: { group.id: 1, }, }) ] if release: frequencies.append( (tsdb.models.frequent_releases_by_groups, { group.id: { grouprelease.id: 1, }, }) ) tsdb.record_frequency_multi(frequencies, timestamp=event.datetime) UserReport.objects.filter( project=project, event_id=event_id, ).update(group=group) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(using=router.db_for_write(Event)): event.save() except IntegrityError: self.logger.info('Duplicate Event found for event_id=%s', event_id, exc_info=True) return event index_event_tags.delay( project_id=project.id, group_id=group.id, event_id=event.id, tags=tags, ) if event_user: tsdb.record_multi(( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value,)), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value,)), ), timestamp=event.datetime) if is_new and release: buffer.incr(Release, {'new_groups': 1}, { 'id': release.id, }) safe_execute(Group.objects.add_tags, group, tags, _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) first_event_received.send(project=project, group=group, sender=Project) post_process_group.delay( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, ) else: self.logger.info('Raw event passed; skipping post process for event_id=%s', event_id) # TODO: move this to the queue if is_regression and not raw: regression_signal.send_robust(sender=Group, instance=group) return event
def get_release(self, project, data): if not data.get("release"): return return Release.get(project=project, version=data["release"])
def save(self, project_id, raw=False, assume_normalized=False): # Normalize if needed if not self._normalized: if not assume_normalized: self.normalize() self._normalized = True data = self._data project = Project.objects.get_from_cache(id=project_id) project._organization_cache = Organization.objects.get_from_cache( id=project.organization_id) # Check to make sure we're not about to do a bunch of work that's # already been done if we've processed an event with this ID. (This # isn't a perfect solution -- this doesn't handle ``EventMapping`` and # there's a race condition between here and when the event is actually # saved, but it's an improvement. See GH-7677.) try: event = Event.objects.get( project_id=project.id, event_id=data['event_id'], ) except Event.DoesNotExist: pass else: # Make sure we cache on the project before returning event._project_cache = project logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': data['event_id'], 'project_id': project.id, 'model': Event.__name__, } ) return event # Pull out the culprit culprit = self.get_culprit() # Pull the toplevel data we're interested in level = data.get('level') # TODO(mitsuhiko): this code path should be gone by July 2018. # This is going to be fine because no code actually still depends # on integers here. When we need an integer it will be converted # into one later. Old workers used to send integers here. if level is not None and isinstance(level, six.integer_types): level = LOG_LEVELS[level] transaction_name = data.get('transaction') logger_name = data.get('logger') release = data.get('release') dist = data.get('dist') environment = data.get('environment') recorded_timestamp = data.get('timestamp') # We need to swap out the data with the one internal to the newly # created event object event = self._get_event_instance(project_id=project_id) self._data = data = event.data.data event._project_cache = project date = event.datetime platform = event.platform event_id = event.event_id if transaction_name: transaction_name = force_text(transaction_name) # Some of the data that are toplevel attributes are duplicated # into tags (logger, level, environment, transaction). These are # different from legacy attributes which are normalized into tags # ahead of time (site, server_name). setdefault_path(data, 'tags', value=[]) set_tag(data, 'level', level) if logger_name: set_tag(data, 'logger', logger_name) if environment: set_tag(data, 'environment', environment) if transaction_name: set_tag(data, 'transaction', transaction_name) if release: # dont allow a conflicting 'release' tag pop_tag(data, 'release') release = Release.get_or_create( project=project, version=release, date_added=date, ) set_tag(data, 'sentry:release', release.version) if dist and release: dist = release.add_dist(dist, date) # dont allow a conflicting 'dist' tag pop_tag(data, 'dist') set_tag(data, 'sentry:dist', dist.name) else: dist = None event_user = self._get_event_user(project, data) if event_user: # dont allow a conflicting 'user' tag pop_tag(data, 'user') set_tag(data, 'sentry:user', event_user.tag_value) # At this point we want to normalize the in_app values in case the # clients did not set this appropriately so far. grouping_config = load_grouping_config( get_grouping_config_dict_for_event_data(data, project)) normalize_stacktraces_for_grouping(data, grouping_config) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: # plugins should not override user provided tags for key, value in added_tags: if get_tag(data, key) is None: set_tag(data, key, value) for path, iface in six.iteritems(event.interfaces): for k, v in iface.iter_tags(): set_tag(data, k, v) # Get rid of ephemeral interface data if iface.ephemeral: data.pop(iface.path, None) # The active grouping config was put into the event in the # normalize step before. We now also make sure that the # fingerprint was set to `'{{ default }}' just in case someone # removed it from the payload. The call to get_hashes will then # look at `grouping_config` to pick the right paramters. data['fingerprint'] = data.get('fingerprint') or ['{{ default }}'] apply_server_fingerprinting(data, get_fingerprinting_config_for_project(project)) hashes = event.get_hashes() data['hashes'] = hashes # we want to freeze not just the metadata and type in but also the # derived attributes. The reason for this is that we push this # data into kafka for snuba processing and our postprocessing # picks up the data right from the snuba topic. For most usage # however the data is dynamically overriden by Event.title and # Event.location (See Event.as_dict) materialized_metadata = self.materialize_metadata() event_metadata = materialized_metadata['metadata'] data.update(materialized_metadata) data['culprit'] = culprit # index components into ``Event.message`` # See GH-3248 event.message = self.get_search_message(event_metadata, culprit) received_timestamp = event.data.get('received') or float(event.datetime.strftime('%s')) # The group gets the same metadata as the event when it's flushed but # additionally the `last_received` key is set. This key is used by # _save_aggregate. group_metadata = dict(materialized_metadata) group_metadata['last_received'] = received_timestamp kwargs = { 'platform': platform, 'message': event.message, 'culprit': culprit, 'logger': logger_name, 'level': LOG_LEVELS_MAP.get(level), 'last_seen': date, 'first_seen': date, 'active_at': date, 'data': group_metadata, } if release: kwargs['first_release'] = release try: group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **kwargs ) except HashDiscarded: event_discarded.send_robust( project=project, sender=EventManager, ) metrics.incr( 'events.discarded', skip_internal=True, tags={ 'organization_id': project.organization_id, 'platform': platform, }, ) raise else: event_saved.send_robust( project=project, event_size=event.size, sender=EventManager, ) event.group = group # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) # When an event was sampled, the canonical source of truth # is the EventMapping table since we aren't going to be writing out an actual # Event row. Otherwise, if the Event isn't being sampled, we can safely # rely on the Event table itself as the source of truth and ignore # EventMapping since it's redundant information. if is_sample: try: with transaction.atomic(using=router.db_for_write(EventMapping)): EventMapping.objects.create(project=project, group=group, event_id=event_id) except IntegrityError: logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': EventMapping.__name__, } ) return event environment = Environment.get_or_create( project=project, name=environment, ) group_environment, is_new_group_environment = GroupEnvironment.get_or_create( group_id=group.id, environment_id=environment.id, defaults={ 'first_release': release if release else None, }, ) if release: ReleaseEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) ReleaseProjectEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) grouprelease = GroupRelease.get_or_create( group=group, release=release, environment=environment, datetime=date, ) counters = [ (tsdb.models.group, group.id), (tsdb.models.project, project.id), ] if release: counters.append((tsdb.models.release, release.id)) tsdb.incr_multi(counters, timestamp=event.datetime, environment_id=environment.id) frequencies = [ # (tsdb.models.frequent_projects_by_organization, { # project.organization_id: { # project.id: 1, # }, # }), # (tsdb.models.frequent_issues_by_project, { # project.id: { # group.id: 1, # }, # }) (tsdb.models.frequent_environments_by_group, { group.id: { environment.id: 1, }, }) ] if release: frequencies.append( (tsdb.models.frequent_releases_by_group, { group.id: { grouprelease.id: 1, }, }) ) tsdb.record_frequency_multi(frequencies, timestamp=event.datetime) UserReport.objects.filter( project=project, event_id=event_id, ).update( group=group, environment=environment, ) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(using=router.db_for_write(Event)): event.save() except IntegrityError: logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': Event.__name__, } ) return event tagstore.delay_index_event_tags( organization_id=project.organization_id, project_id=project.id, group_id=group.id, environment_id=environment.id, event_id=event.id, tags=event.tags, date_added=event.datetime, ) if event_user: tsdb.record_multi( ( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value, )), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value, )), ), timestamp=event.datetime, environment_id=environment.id, ) if release: if is_new: buffer.incr( ReleaseProject, {'new_groups': 1}, { 'release_id': release.id, 'project_id': project.id, } ) if is_new_group_environment: buffer.incr( ReleaseProjectEnvironment, {'new_issues_count': 1}, { 'project_id': project.id, 'release_id': release.id, 'environment_id': environment.id, } ) safe_execute( Group.objects.add_tags, group, environment, event.get_tags(), _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) first_event_received.send_robust(project=project, group=group, sender=Project) eventstream.insert( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, is_new_group_environment=is_new_group_environment, primary_hash=hashes[0], # We are choosing to skip consuming the event back # in the eventstream if it's flagged as raw. # This means that we want to publish the event # through the event stream, but we don't care # about post processing and handling the commit. skip_consume=raw, ) metrics.timing( 'events.latency', received_timestamp - recorded_timestamp, tags={ 'project_id': project.id, }, ) metrics.timing( 'events.size.data.post_save', event.size, tags={'project_id': project.id} ) return event
def validate_version(self, value): if not Release.is_valid_version(value): raise serializers.ValidationError("Release with name %s is not allowed" % value) return value
def save(self, project, raw=False): # TODO: culprit should default to "most recent" frame in stacktraces when # it's not provided. project = Project.objects.get_from_cache(id=project) data = self.data.copy() # First we pull out our top-level (non-data attr) kwargs event_id = data.pop('event_id') message = data.pop('message') level = data.pop('level') culprit = data.pop('culprit', None) or '' time_spent = data.pop('time_spent', None) logger_name = data.pop('logger', None) server_name = data.pop('server_name', None) site = data.pop('site', None) checksum = data.pop('checksum', None) platform = data.pop('platform', None) release = data.pop('release', None) date = datetime.fromtimestamp(data.pop('timestamp')) date = date.replace(tzinfo=timezone.utc) kwargs = { 'message': message, 'platform': platform, } event = Event( project=project, event_id=event_id, data=data, time_spent=time_spent, datetime=date, **kwargs ) tags = data.get('tags') or [] tags.append(('level', LOG_LEVELS[level])) if logger_name: tags.append(('logger', logger_name)) if server_name: tags.append(('server_name', server_name)) if site: tags.append(('site', site)) if release: # TODO(dcramer): we should ensure we create Release objects tags.append(('sentry:release', release)) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: tags.extend(added_tags) # XXX(dcramer): we're relying on mutation of the data object to ensure # this propagates into Event data['tags'] = tags # Calculate the checksum from the first highest scoring interface if checksum: hashes = [checksum] else: hashes = get_hashes_for_event(event) group_kwargs = kwargs.copy() group_kwargs.update({ 'culprit': culprit, 'logger': logger_name, 'level': level, 'last_seen': date, 'first_seen': date, 'time_spent_total': time_spent or 0, 'time_spent_count': time_spent and 1 or 0, }) if release: group_kwargs['first_release'] = Release.get_or_create( project=project, version=release, date_added=date, ) Activity.objects.create( type=Activity.RELEASE, project=project, ident=release, data={'version': release}, datetime=date, ) group, is_new, is_regression, is_sample = safe_execute( self._save_aggregate, event=event, hashes=hashes, **group_kwargs ) using = group._state.db event.group = group try: with transaction.atomic(): EventMapping.objects.create( project=project, group=group, event_id=event_id) except IntegrityError: self.logger.info('Duplicate EventMapping found for event_id=%s', event_id) return event UserReport.objects.filter( project=project, event_id=event_id, ).update(group=group) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(): event.save() except IntegrityError: self.logger.info('Duplicate Event found for event_id=%s', event_id) return event safe_execute(Group.objects.add_tags, group, tags, _with_transaction=False) if not raw: post_process_group.delay( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, ) else: self.logger.info('Raw event passed; skipping post process for event_id=%s', event_id) index_event.delay(event) # TODO: move this to the queue if is_regression and not raw: regression_signal.send_robust(sender=Group, instance=group) return event
def debounce_update_release_health_data(organization, project_ids): """This causes a flush of snuba health data to the postgres tables once per minute for the given projects. """ # Figure out which projects need to get updates from the snuba. should_update = {} cache_keys = ["debounce-health:%d" % id for id in project_ids] cache_data = cache.get_many(cache_keys) for project_id, cache_key in izip(project_ids, cache_keys): if cache_data.get(cache_key) is None: should_update[project_id] = cache_key if not should_update: return projects = { p.id: p for p in Project.objects.get_many_from_cache(should_update.keys()) } # This gives us updates for all release-projects which have seen new # health data over the last days. It will miss releases where the last # date is longer than what `get_changed_project_release_model_adoptions` # considers recent. project_releases = release_health.get_changed_project_release_model_adoptions( should_update.keys()) # Check which we already have rows for. existing = set( ReleaseProject.objects.filter( project_id__in=[x[0] for x in project_releases], release__version__in=[x[1] for x in project_releases], ).values_list("project_id", "release__version")) to_upsert = [] for key in project_releases: if key not in existing: to_upsert.append(key) if to_upsert: dates = release_health.get_oldest_health_data_for_releases(to_upsert) for project_id, version in to_upsert: project = projects.get(project_id) if project is None: # should not happen continue # We might have never observed the release. This for instance can # happen if the release only had health data so far. For these cases # we want to create the release the first time we observed it on the # health side. release = Release.get_or_create(project=project, version=version, date_added=dates.get( (project_id, version))) # Make sure that the release knows about this project. Like we had before # the project might not have been associated with this release yet. release.add_project(project) # Debounce updates for a minute cache.set_many( dict(izip(should_update.values(), [True] * len(should_update))), 60)
def create_message_event(template, parameters, environment, release): i = next(sequence) event_id = uuid.UUID(fields=( i, 0x0, 0x1000, 0x80, 0x80, 0x808080808080, ), ).hex tags = [['color', next(tag_values)]] if environment: tags.append(['environment', environment]) if release: tags.append(['sentry:release', release]) event = Event.objects.create( project_id=project.id, group_id=source.id, event_id=event_id, message='%s' % (id, ), datetime=now + shift(i), data={ 'environment': environment, 'type': 'default', 'metadata': { 'title': template % parameters, }, 'logentry': { 'message': template, 'params': parameters, 'formatted': template % parameters, }, 'user': next(user_values), 'tags': tags, }, ) with self.tasks(): Group.objects.add_tags( source, Environment.objects.get( organization_id=project.organization_id, name=environment), tags=event.tags, ) EventMapping.objects.create( project_id=project.id, group_id=source.id, event_id=event_id, date_added=event.datetime, ) UserReport.objects.create( project_id=project.id, group_id=source.id, event_id=event_id, name='Log Hat', email='*****@*****.**', comments='Quack', ) if release: Release.get_or_create( project=project, version=event.get_tag('sentry:release'), date_added=event.datetime, ) features.record([event]) return event
def test_simple(self): org = self.create_organization() commit = Commit.objects.create(organization_id=org.id, repository_id=5) commit2 = Commit.objects.create(organization_id=org.id, repository_id=6) # merge to project = self.create_project(organization=org, name='foo') release = Release.objects.create(version='abcdabc', organization=org) release.add_project(project) release_commit = ReleaseCommit.objects.create(organization_id=org.id, release=release, commit=commit, order=1) release_environment = ReleaseEnvironment.objects.create( organization_id=org.id, project_id=project.id, release_id=release.id, environment_id=2) group_release = GroupRelease.objects.create(project_id=project.id, release_id=release.id, group_id=1) group = self.create_group(project=project, first_release=release) group_resolution = GroupResolution.objects.create(group=group, release=release) # merge from #1 project2 = self.create_project(organization=org, name='bar') release2 = Release.objects.create(version='bbbbbbb', organization=org) release2.add_project(project2) release_commit2 = ReleaseCommit.objects.create(organization_id=org.id, release=release2, commit=commit, order=2) release_environment2 = ReleaseEnvironment.objects.create( organization_id=org.id, project_id=project2.id, release_id=release2.id, environment_id=3, ) group_release2 = GroupRelease.objects.create(project_id=project2.id, release_id=release2.id, group_id=2) group2 = self.create_group(project=project2, first_release=release2) group_resolution2 = GroupResolution.objects.create(group=group2, release=release2) # merge from #2 project3 = self.create_project(organization=org, name='baz') release3 = Release.objects.create(version='cccccc', organization=org) release3.add_project(project3) release_commit3 = ReleaseCommit.objects.create(organization_id=org.id, release=release2, commit=commit2, order=3) release_environment3 = ReleaseEnvironment.objects.create( organization_id=org.id, project_id=project3.id, release_id=release3.id, environment_id=4, ) group_release3 = GroupRelease.objects.create(project_id=project3.id, release_id=release3.id, group_id=3) group3 = self.create_group(project=project3, first_release=release3) group_resolution3 = GroupResolution.objects.create(group=group3, release=release3) Release.merge(release, [release2, release3]) # ReleaseCommit.release assert ReleaseCommit.objects.get( id=release_commit.id).release == release # should not exist because they referenced the same commit assert not ReleaseCommit.objects.filter(id=release_commit2.id).exists() assert ReleaseCommit.objects.get( id=release_commit3.id).release == release # ReleaseEnvironment.release_id assert ReleaseEnvironment.objects.get( id=release_environment.id).release_id == release.id assert ReleaseEnvironment.objects.get( id=release_environment2.id).release_id == release.id assert ReleaseEnvironment.objects.get( id=release_environment3.id).release_id == release.id # ReleaseProject.release assert release.projects.count() == 3 assert ReleaseProject.objects.filter(release=release, project=project).exists() assert ReleaseProject.objects.filter(release=release, project=project2).exists() assert ReleaseProject.objects.filter(release=release, project=project3).exists() # GroupRelease.release_id assert GroupRelease.objects.get( id=group_release.id).release_id == release.id assert GroupRelease.objects.get( id=group_release2.id).release_id == release.id assert GroupRelease.objects.get( id=group_release3.id).release_id == release.id # GroupResolution.release assert GroupResolution.objects.get( id=group_resolution.id).release == release assert GroupResolution.objects.get( id=group_resolution2.id).release == release assert GroupResolution.objects.get( id=group_resolution3.id).release == release # Group.first_release assert Group.objects.get(id=group.id).first_release == release assert Group.objects.get(id=group2.id).first_release == release assert Group.objects.get(id=group3.id).first_release == release # Releases are gone assert Release.objects.filter(id=release.id).exists() assert not Release.objects.filter(id=release2.id).exists() assert not Release.objects.filter(id=release3.id).exists()
def get(self, request): org = Organization( id=1, slug='organization', name='My Company', ) projects = [ Project( id=1, organization=org, slug='project', name='My Project', ), Project( id=2, organization=org, slug='another-project', name='Another Project', ), Project( id=3, organization=org, slug='yet-another-project', name='Yet Another Project', ), ] release = Release(organization_id=org.id, version='6c998f755f304593a4713abd123eaf8833a2de5e', date_added=datetime.datetime(2016, 10, 12, 15, 39, tzinfo=pytz.utc)) deploy = Deploy( release=release, organization_id=org.id, environment_id=1, date_finished=datetime.datetime(2016, 10, 12, 15, 39, tzinfo=pytz.utc), ) has_new_links = features.has('organizations:sentry10', org) if has_new_links: release_links = [ absolute_uri( u'/organizations/{}/releases/{}/?project={}'.format( org.slug, release.version, p.id, )) for p in projects ] else: release_links = [ absolute_uri(u'/{}/{}/releases/{}/'.format( org.slug, p.slug, release.version, )) for p in projects ] repos = [{ 'name': 'getsentry/getsentry', 'commits': [ (Commit( key='48b86fcd677da3dba5679d7a738240ce6fb74b20', date_added=datetime.datetime(2016, 10, 11, 15, 39, tzinfo=pytz.utc), ), None), (Commit( key='a53a2756bb8d111b43196210b34df90b87ed336b', message='Fix billing', author=CommitAuthor( name='David Cramer', email='*****@*****.**', ), date_added=datetime.datetime(2016, 10, 11, 16, 45, tzinfo=pytz.utc), ), User(email='*****@*****.**', name='David Cramer')), ], }, { 'name': 'getsentry/sentry', 'commits': [ (Commit( key='3c8eb3b4af6ee2a29c68daa188fc730c8e4b39fd', date_added=datetime.datetime(2016, 10, 10, 15, 39, tzinfo=pytz.utc), ), None), (Commit( key='373562702009df1692da6eb80a933139f29e094b', message='Fix padding', author=CommitAuthor( name='Chris Jennings', email='*****@*****.**', ), date_added=datetime.datetime(2016, 10, 10, 16, 39, tzinfo=pytz.utc), ), None), (Commit( key='631cd9096bd9811a046a472bb0aa8b573e86e1f1', message='Update README.rst', author=CommitAuthor( name='David Cramer', email='*****@*****.**', ), date_added=datetime.datetime(2016, 10, 11, 10, 39, tzinfo=pytz.utc), ), User(email='*****@*****.**', name='David Cramer')), ], }] return MailPreview( html_template='sentry/emails/activity/release.html', text_template='sentry/emails/activity/release.txt', context={ 'release': release, 'projects': zip(projects, release_links, [6, 1, 0]), 'repos': repos, 'reason': GroupSubscriptionReason.descriptions[ GroupSubscriptionReason.committed], 'project_count': len(projects), 'commit_count': 4, 'author_count': 1, 'file_count': 5, 'environment': 'production', 'deploy': deploy, 'setup_repo_link': absolute_uri('/organizations/{}/repos/'.format(org.slug, )), }, ).render(request)
def validate_version(self, attrs, source): value = attrs[source] if not Release.is_valid_version(value): raise serializers.ValidationError('Invalid value for release') return attrs
def merge_to(from_org, to_org): from sentry.models import ( ApiKey, AuditLogEntry, AuthProvider, Commit, Environment, OrganizationAvatar, OrganizationIntegration, OrganizationMember, OrganizationMemberTeam, Project, Release, ReleaseCommit, ReleaseEnvironment, ReleaseFile, ReleaseHeadCommit, Repository, Team, ) for from_member in OrganizationMember.objects.filter( organization=from_org, user__isnull=False): logger = logging.getLogger("sentry.merge") try: to_member = OrganizationMember.objects.get( organization=to_org, user=from_member.user) except OrganizationMember.DoesNotExist: from_member.update(organization=to_org) to_member = from_member else: qs = OrganizationMemberTeam.objects.filter( organizationmember=from_member, is_active=True).select_related() for omt in qs: OrganizationMemberTeam.objects.create_or_update( organizationmember=to_member, team=omt.team, defaults={"is_active": True}) logger.info( "user.migrate", extra={ "instance_id": from_member.id, "new_member_id": to_member.id, "from_organization_id": from_org.id, "to_organization_id": to_org.id, }, ) for from_team in Team.objects.filter(organization=from_org): try: with transaction.atomic(): from_team.update(organization=to_org) except IntegrityError: slugify_instance(from_team, from_team.name, organization=to_org) from_team.update(organization=to_org, slug=from_team.slug) logger.info( "team.migrate", extra={ "instance_id": from_team.id, "new_slug": from_team.slug, "from_organization_id": from_org.id, "to_organization_id": to_org.id, }, ) for from_project in Project.objects.filter(organization=from_org): try: with transaction.atomic(): from_project.update(organization=to_org) except IntegrityError: slugify_instance( from_project, from_project.name, organization=to_org, reserved=RESERVED_PROJECT_SLUGS, ) from_project.update(organization=to_org, slug=from_project.slug) logger.info( "project.migrate", extra={ "instance_id": from_project.id, "new_slug": from_project.slug, "from_organization_id": from_org.id, "to_organization_id": to_org.id, }, ) # TODO(jess): update this when adding unique constraint # on version, organization for releases for from_release in Release.objects.filter(organization=from_org): try: to_release = Release.objects.get(version=from_release.version, organization=to_org) except Release.DoesNotExist: Release.objects.filter(id=from_release.id).update( organization=to_org) else: Release.merge(to_release, [from_release]) logger.info( "release.migrate", extra={ "instance_id": from_release.id, "from_organization_id": from_org.id, "to_organization_id": to_org.id, }, ) def do_update(queryset, params): model_name = queryset.model.__name__.lower() try: with transaction.atomic(): queryset.update(**params) except IntegrityError: for instance in queryset: try: with transaction.atomic(): instance.update(**params) except IntegrityError: logger.info( f"{model_name}.migrate-skipped", extra={ "from_organization_id": from_org.id, "to_organization_id": to_org.id, }, ) else: logger.info( f"{model_name}.migrate", extra={ "instance_id": instance.id, "from_organization_id": from_org.id, "to_organization_id": to_org.id, }, ) else: logger.info( f"{model_name}.migrate", extra={ "from_organization_id": from_org.id, "to_organization_id": to_org.id }, ) INST_MODEL_LIST = ( AuthProvider, ApiKey, AuditLogEntry, OrganizationAvatar, OrganizationIntegration, ReleaseEnvironment, ReleaseFile, ) ATTR_MODEL_LIST = (Commit, ReleaseCommit, ReleaseHeadCommit, Repository, Environment) for model in INST_MODEL_LIST: queryset = model.objects.filter(organization=from_org) do_update(queryset, {"organization": to_org}) for model in ATTR_MODEL_LIST: queryset = model.objects.filter(organization_id=from_org.id) do_update(queryset, {"organization_id": to_org.id})
def set_commits(self, version, commit_list): """ Commits should be ordered oldest to newest. Calling this method will remove all existing commit history. """ project = self.project release = Release.objects.filter( organization_id=project.organization_id, version=version, projects=self.project ).first() if not release: release = Release.objects.filter( organization_id=project.organization_id, version=version, ).first() if not release: lock_key = Release.get_lock_key(project.organization_id, version) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release = Release.objects.get( organization_id=project.organization_id, version=version ) except Release.DoesNotExist: release = Release.objects.create( organization_id=project.organization_id, version=version ) release.add_project(project) with transaction.atomic(): # TODO(dcramer): would be good to optimize the logic to avoid these # deletes but not overly important ReleaseCommit.objects.filter( release=release, ).delete() authors = {} repos = {} for idx, data in enumerate(commit_list): repo_name = data.get('repository') or 'project-{}'.format(project.id) if repo_name not in repos: repos[repo_name] = repo = Repository.objects.get_or_create( organization_id=project.organization_id, name=repo_name, )[0] else: repo = repos[repo_name] author_email = data.get('author_email') if author_email is None and data.get('author_name'): author_email = self._to_email(data['author_name']) if not author_email: author = None elif author_email not in authors: authors[author_email] = author = CommitAuthor.objects.get_or_create( organization_id=project.organization_id, email=author_email, defaults={ 'name': data.get('author_name'), } )[0] if data.get('author_name') and author.name != data['author_name']: author.update(name=data['author_name']) else: author = authors[author_email] commit = Commit.objects.get_or_create( organization_id=project.organization_id, repository_id=repo.id, key=data['id'], defaults={ 'message': data.get('message'), 'author': author, 'date_added': data.get('timestamp') or timezone.now(), } )[0] ReleaseCommit.objects.create( organization_id=project.organization_id, release=release, commit=commit, order=idx, )
def post(self, request, project): """ Create a New Release ```````````````````` Create a new release and/or associate a project with a release. Release versions that are the same across multiple projects within an Organization will be treated as the same release in Sentry. Releases are used by Sentry to improve its error reporting abilities by correlating first seen events with the release that might have introduced the problem. Releases are also necessary for sourcemaps and other debug features that require manual upload for functioning well. :pparam string organization_slug: the slug of the organization the release belongs to. :pparam string project_slug: the slug of the project to create a release for. :param string version: a version identifier for this release. Can be a version number, a commit hash etc. :param string ref: an optional commit reference. This is useful if a tagged version has been provided. :param url url: a URL that points to the release. This can be the path to an online interface to the sourcecode for instance. :param datetime dateStarted: an optional date that indicates when the release process started. :param datetime dateReleased: an optional date that indicates when the release went live. If not provided the current time is assumed. :auth: required """ serializer = ReleaseSerializer(data=request.DATA) if serializer.is_valid(): result = serializer.object # release creation is idempotent to simplify user # experiences release = Release.objects.filter( organization_id=project.organization_id, version=result['version'], projects=project ).first() created = False if release: was_released = bool(release.date_released) else: release = Release.objects.filter( organization_id=project.organization_id, version=result['version'], ).first() if not release: lock_key = Release.get_lock_key(project.organization_id, result['version']) lock = locks.get(lock_key, duration=5) with TimedRetryPolicy(10)(lock.acquire): try: release, created = Release.objects.get( version=result['version'], organization_id=project.organization_id ), False except Release.DoesNotExist: release, created = Release.objects.create( organization_id=project.organization_id, version=result['version'], ref=result.get('ref'), url=result.get('url'), owner=result.get('owner'), date_started=result.get('dateStarted'), date_released=result.get('dateReleased'), ), True was_released = False try: with transaction.atomic(): ReleaseProject.objects.create(project=project, release=release) created = True except IntegrityError: pass commit_list = result.get('commits') if commit_list: hook = ReleaseHook(project) # TODO(dcramer): handle errors with release payloads hook.set_commits(release.version, commit_list) if (not was_released and release.date_released): activity = Activity.objects.create( type=Activity.RELEASE, project=project, ident=result['version'], data={'version': result['version']}, datetime=release.date_released, ) activity.send_notification() if not created: # This is the closest status code that makes sense, and we want # a unique 2xx response code so people can understand when # behavior differs. # 208 Already Reported (WebDAV; RFC 5842) status = 208 else: status = 201 return Response(serialize(release, request.user), status=status) return Response(serializer.errors, status=400)
def save(self, project_id, raw=False, assume_normalized=False): # Normalize if needed if not self._normalized: if not assume_normalized: self.normalize() self._normalized = True data = self._data project = Project.objects.get_from_cache(id=project_id) project._organization_cache = Organization.objects.get_from_cache( id=project.organization_id) # Check to make sure we're not about to do a bunch of work that's # already been done if we've processed an event with this ID. (This # isn't a perfect solution -- this doesn't handle ``EventMapping`` and # there's a race condition between here and when the event is actually # saved, but it's an improvement. See GH-7677.) try: event = Event.objects.get( project_id=project.id, event_id=data['event_id'], ) except Event.DoesNotExist: pass else: # Make sure we cache on the project before returning event._project_cache = project logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': data['event_id'], 'project_id': project.id, 'model': Event.__name__, } ) return event # Pull out the culprit culprit = self.get_culprit() # Pull the toplevel data we're interested in level = data.get('level') # TODO(mitsuhiko): this code path should be gone by July 2018. # This is going to be fine because no code actually still depends # on integers here. When we need an integer it will be converted # into one later. Old workers used to send integers here. if level is not None and isinstance(level, six.integer_types): level = LOG_LEVELS[level] transaction_name = data.get('transaction') logger_name = data.get('logger') release = data.get('release') dist = data.get('dist') environment = data.get('environment') recorded_timestamp = data.get('timestamp') # We need to swap out the data with the one internal to the newly # created event object event = self._get_event_instance(project_id=project_id) self._data = data = event.data.data event._project_cache = project date = event.datetime platform = event.platform event_id = event.event_id if transaction_name: transaction_name = force_text(transaction_name) # Some of the data that are toplevel attributes are duplicated # into tags (logger, level, environment, transaction). These are # different from legacy attributes which are normalized into tags # ahead of time (site, server_name). setdefault_path(data, 'tags', value=[]) set_tag(data, 'level', level) if logger_name: set_tag(data, 'logger', logger_name) if environment: set_tag(data, 'environment', environment) if transaction_name: set_tag(data, 'transaction', transaction_name) if release: # dont allow a conflicting 'release' tag pop_tag(data, 'release') release = Release.get_or_create( project=project, version=release, date_added=date, ) set_tag(data, 'sentry:release', release.version) if dist and release: dist = release.add_dist(dist, date) # dont allow a conflicting 'dist' tag pop_tag(data, 'dist') set_tag(data, 'sentry:dist', dist.name) else: dist = None event_user = self._get_event_user(project, data) if event_user: # dont allow a conflicting 'user' tag pop_tag(data, 'user') set_tag(data, 'sentry:user', event_user.tag_value) # At this point we want to normalize the in_app values in case the # clients did not set this appropriately so far. grouping_config = load_grouping_config( get_grouping_config_dict_for_event_data(data, project)) normalize_stacktraces_for_grouping(data, grouping_config) for plugin in plugins.for_project(project, version=None): added_tags = safe_execute(plugin.get_tags, event, _with_transaction=False) if added_tags: # plugins should not override user provided tags for key, value in added_tags: if get_tag(data, key) is None: set_tag(data, key, value) for path, iface in six.iteritems(event.interfaces): for k, v in iface.iter_tags(): set_tag(data, k, v) # Get rid of ephemeral interface data if iface.ephemeral: data.pop(iface.path, None) # The active grouping config was put into the event in the # normalize step before. We now also make sure that the # fingerprint was set to `'{{ default }}' just in case someone # removed it from the payload. The call to get_hashes will then # look at `grouping_config` to pick the right paramters. data['fingerprint'] = data.get('fingerprint') or ['{{ default }}'] apply_server_fingerprinting(data, get_fingerprinting_config_for_project(project)) # Here we try to use the grouping config that was requested in the # event. If that config has since been deleted (because it was an # experimental grouping config) we fall back to the default. try: hashes = event.get_hashes() except GroupingConfigNotFound: data['grouping_config'] = get_grouping_config_dict_for_project(project) hashes = event.get_hashes() data['hashes'] = hashes # we want to freeze not just the metadata and type in but also the # derived attributes. The reason for this is that we push this # data into kafka for snuba processing and our postprocessing # picks up the data right from the snuba topic. For most usage # however the data is dynamically overriden by Event.title and # Event.location (See Event.as_dict) materialized_metadata = self.materialize_metadata() event_metadata = materialized_metadata['metadata'] data.update(materialized_metadata) data['culprit'] = culprit # index components into ``Event.message`` # See GH-3248 event.message = self.get_search_message(event_metadata, culprit) received_timestamp = event.data.get('received') or float(event.datetime.strftime('%s')) # The group gets the same metadata as the event when it's flushed but # additionally the `last_received` key is set. This key is used by # _save_aggregate. group_metadata = dict(materialized_metadata) group_metadata['last_received'] = received_timestamp kwargs = { 'platform': platform, 'message': event.message, 'culprit': culprit, 'logger': logger_name, 'level': LOG_LEVELS_MAP.get(level), 'last_seen': date, 'first_seen': date, 'active_at': date, 'data': group_metadata, } if release: kwargs['first_release'] = release try: group, is_new, is_regression, is_sample = self._save_aggregate( event=event, hashes=hashes, release=release, **kwargs ) except HashDiscarded: event_discarded.send_robust( project=project, sender=EventManager, ) metrics.incr( 'events.discarded', skip_internal=True, tags={ 'organization_id': project.organization_id, 'platform': platform, }, ) raise else: event_saved.send_robust( project=project, event_size=event.size, sender=EventManager, ) event.group = group # store a reference to the group id to guarantee validation of isolation event.data.bind_ref(event) # When an event was sampled, the canonical source of truth # is the EventMapping table since we aren't going to be writing out an actual # Event row. Otherwise, if the Event isn't being sampled, we can safely # rely on the Event table itself as the source of truth and ignore # EventMapping since it's redundant information. if is_sample: try: with transaction.atomic(using=router.db_for_write(EventMapping)): EventMapping.objects.create(project=project, group=group, event_id=event_id) except IntegrityError: logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': EventMapping.__name__, } ) return event environment = Environment.get_or_create( project=project, name=environment, ) group_environment, is_new_group_environment = GroupEnvironment.get_or_create( group_id=group.id, environment_id=environment.id, defaults={ 'first_release': release if release else None, }, ) if release: ReleaseEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) ReleaseProjectEnvironment.get_or_create( project=project, release=release, environment=environment, datetime=date, ) grouprelease = GroupRelease.get_or_create( group=group, release=release, environment=environment, datetime=date, ) counters = [ (tsdb.models.group, group.id), (tsdb.models.project, project.id), ] if release: counters.append((tsdb.models.release, release.id)) tsdb.incr_multi(counters, timestamp=event.datetime, environment_id=environment.id) frequencies = [ # (tsdb.models.frequent_projects_by_organization, { # project.organization_id: { # project.id: 1, # }, # }), # (tsdb.models.frequent_issues_by_project, { # project.id: { # group.id: 1, # }, # }) (tsdb.models.frequent_environments_by_group, { group.id: { environment.id: 1, }, }) ] if release: frequencies.append( (tsdb.models.frequent_releases_by_group, { group.id: { grouprelease.id: 1, }, }) ) tsdb.record_frequency_multi(frequencies, timestamp=event.datetime) UserReport.objects.filter( project=project, event_id=event_id, ).update( group=group, environment=environment, ) # Update any event attachment that arrived before the event group was defined. EventAttachment.objects.filter( project_id=project.id, event_id=event_id, ).update( group_id=group.id, ) # save the event unless its been sampled if not is_sample: try: with transaction.atomic(using=router.db_for_write(Event)): event.save() except IntegrityError: logger.info( 'duplicate.found', exc_info=True, extra={ 'event_uuid': event_id, 'project_id': project.id, 'group_id': group.id, 'model': Event.__name__, } ) return event tagstore.delay_index_event_tags( organization_id=project.organization_id, project_id=project.id, group_id=group.id, environment_id=environment.id, event_id=event.id, tags=event.tags, date_added=event.datetime, ) if event_user: tsdb.record_multi( ( (tsdb.models.users_affected_by_group, group.id, (event_user.tag_value, )), (tsdb.models.users_affected_by_project, project.id, (event_user.tag_value, )), ), timestamp=event.datetime, environment_id=environment.id, ) if release: if is_new: buffer.incr( ReleaseProject, {'new_groups': 1}, { 'release_id': release.id, 'project_id': project.id, } ) if is_new_group_environment: buffer.incr( ReleaseProjectEnvironment, {'new_issues_count': 1}, { 'project_id': project.id, 'release_id': release.id, 'environment_id': environment.id, } ) safe_execute( Group.objects.add_tags, group, environment, event.get_tags(), _with_transaction=False) if not raw: if not project.first_event: project.update(first_event=date) first_event_received.send_robust(project=project, group=group, sender=Project) eventstream.insert( group=group, event=event, is_new=is_new, is_sample=is_sample, is_regression=is_regression, is_new_group_environment=is_new_group_environment, primary_hash=hashes[0], # We are choosing to skip consuming the event back # in the eventstream if it's flagged as raw. # This means that we want to publish the event # through the event stream, but we don't care # about post processing and handling the commit. skip_consume=raw, ) metrics.timing( 'events.latency', received_timestamp - recorded_timestamp, tags={ 'project_id': project.id, }, ) metrics.timing( 'events.size.data.post_save', event.size, tags={'project_id': project.id} ) return event
def get(self, _, project, event_id): """ Retrieve Committer information for an event ``````````````````````````````````````````` Return commiters on an individual event, plus a per-frame breakdown. :pparam string project_slug: the slug of the project the event belongs to. :pparam string event_id: the hexadecimal ID of the event to retrieve (as reported by the raven client). :auth: required """ try: event = Event.objects.get( id=event_id, project_id=project.id, ) except Event.DoesNotExist: return Response({'detail': 'Event not found'}, status=404) # populate event data Event.objects.bind_nodes([event], 'data') group = Group.objects.get(id=event.group_id) first_release_version = group.get_first_release() if not first_release_version: return Response({'detail': 'Release not found'}, status=404) releases = Release.get_closest_releases(project, first_release_version) if not releases: return Response({'detail': 'Release not found'}, status=404) commits = self._get_commits(releases) if not commits: return Response({'detail': 'No Commits found for Release'}, status=404) frames = self._get_frame_paths(event) frame_limit = 15 app_frames = [frame for frame in frames if frame['in_app']][:frame_limit] # TODO(maxbittker) return this set instead of annotated frames path_set = {frame['abs_path'] for frame in app_frames} file_changes = [] if path_set: file_changes = self._get_commit_file_changes(commits, path_set) commit_path_matches = { path: self._match_commits_path(file_changes, path) for path in path_set } annotated_frames = [ { 'frame': frame, 'commits': commit_path_matches[frame['abs_path']] } for frame in app_frames ] relevant_commits = list( {commit for match in commit_path_matches for commit in commit_path_matches[match]} ) committers = self._get_committers(annotated_frames, relevant_commits) # serialize the commit objects serialized_annotated_frames = [ { 'frame': frame['frame'], 'commits': serialize(frame['commits']) } for frame in annotated_frames ] data = { # map author ids to sentry user dicts 'committers': committers, 'annotatedFrames': serialized_annotated_frames } return Response(data)
def validate_version(self, attrs, source): value = attrs[source] if not Release.is_valid_version(value): raise serializers.ValidationError( 'Release with name %s is not allowed' % value) return attrs
def validate_version(self, attrs, source): value = attrs[source] if not Release.is_valid_version(value): raise serializers.ValidationError('Release with name %s is not allowed' % value) return attrs