def test_no_commits(self): event = self.store_event( data={ "timestamp": iso_format(before_now(seconds=1)), "message": "Kaboom!", "stacktrace": { "frames": [ { "function": "handle_set_commits", "abs_path": "/usr/src/sentry/src/sentry/tasks.py", "module": "sentry.tasks", "in_app": True, "lineno": 30, "filename": "sentry/tasks.py", }, { "function": "set_commits", "abs_path": "/usr/src/sentry/src/sentry/models/release.py", "module": "sentry.models.release", "in_app": True, "lineno": 39, "filename": "sentry/models/release.py", }, ] }, "tags": { "sentry:release": self.release.version }, }, project_id=self.project.id, ) with self.assertRaises(Commit.DoesNotExist): get_serialized_event_file_committers(self.project, event)
def test_no_commits(self): event = self.create_event( group=self.group, message="Kaboom!", platform="python", stacktrace={ "frames": [ { "function": "handle_set_commits", "abs_path": "/usr/src/sentry/src/sentry/tasks.py", "module": "sentry.tasks", "in_app": True, "lineno": 30, "filename": "sentry/tasks.py", }, { "function": "set_commits", "abs_path": "/usr/src/sentry/src/sentry/models/release.py", "module": "sentry.models.release", "in_app": True, "lineno": 39, "filename": "sentry/models/release.py", }, ] }, ) with self.assertRaises(Commit.DoesNotExist): get_serialized_event_file_committers(self.project, event)
def test_java_sdk_path_mangling(self): event = self.store_event( data={ "message": "Kaboom!", "platform": "java", "stacktrace": { "frames": [ { "function": "invoke0", "abs_path": "NativeMethodAccessorImpl.java", "in_app": False, "module": "jdk.internal.reflect.NativeMethodAccessorImpl", "filename": "NativeMethodAccessorImpl.java", }, { "function": "home", "abs_path": "Application.java", "module": "io.sentry.example.Application", "in_app": True, "lineno": 30, "filename": "Application.java", }, { "function": "handledError", "abs_path": "Application.java", "module": "io.sentry.example.Application", "in_app": True, "lineno": 39, "filename": "Application.java", }, ] }, "tags": {"sentry:release": self.release.version}, }, project_id=self.project.id, ) self.release.set_commits( [ { "id": "a" * 40, "repository": self.repo.name, "author_email": "*****@*****.**", "author_name": "Bob", "message": "i fixed a bug", "patch_set": [ {"path": "src/main/java/io/sentry/example/Application.java", "type": "M"} ], } ] ) GroupRelease.objects.create( group_id=event.group.id, project_id=self.project.id, release_id=self.release.id ) result = get_serialized_event_file_committers(self.project, event) assert len(result) == 1 assert "commits" in result[0] assert len(result[0]["commits"]) == 1 assert result[0]["commits"][0]["id"] == "a" * 40
def test_matching(self): event = self.store_event( data={ "message": "Kaboom!", "platform": "python", "timestamp": iso_format(before_now(seconds=1)), "stacktrace": { "frames": [ { "function": "handle_set_commits", "abs_path": "/usr/src/sentry/src/sentry/tasks.py", "module": "sentry.tasks", "in_app": True, "lineno": 30, "filename": "sentry/tasks.py", }, { "function": "set_commits", "abs_path": "/usr/src/sentry/src/sentry/models/release.py", "module": "sentry.models.release", "in_app": True, "lineno": 39, "filename": "sentry/models/release.py", }, ] }, "tags": { "sentry:release": self.release.version }, }, project_id=self.project.id, ) self.release.set_commits([{ "id": "a" * 40, "repository": self.repo.name, "author_email": "*****@*****.**", "author_name": "Bob", "message": "i fixed a bug", "patch_set": [{ "path": "src/sentry/models/release.py", "type": "M" }], }]) GroupRelease.objects.create(group_id=event.group.id, project_id=self.project.id, release_id=self.release.id) result = get_serialized_event_file_committers(self.project, event) assert len(result) == 1 assert "commits" in result[0] assert len(result[0]["commits"]) == 1 assert result[0]["commits"][0]["id"] == "a" * 40
def test_no_matching_user(self): self.set_release_commits("*****@*****.**") result = get_serialized_event_file_committers(self.project, self.event) assert len(result) == 1 assert "commits" in result[0] assert len(result[0]["commits"]) == 1 assert result[0]["commits"][0]["id"] == "a" * 40 assert not GroupOwner.objects.filter(group=self.event.group).exists() process_suspect_commits(self.event) assert not GroupOwner.objects.filter(group=self.event.group).exists()
def test_java_sdk_path_mangling(self): event = self.create_event( group=self.group, message='Kaboom!', platform='java', stacktrace={ 'frames': [{ "function": "invoke0", "abs_path": "NativeMethodAccessorImpl.java", "in_app": False, "module": "jdk.internal.reflect.NativeMethodAccessorImpl", "filename": "NativeMethodAccessorImpl.java", }, { "function": "home", "abs_path": "Application.java", "module": "io.sentry.example.Application", "in_app": True, "lineno": 30, "filename": "Application.java", }, { "function": "handledError", "abs_path": "Application.java", "module": "io.sentry.example.Application", "in_app": True, "lineno": 39, "filename": "Application.java", }] }) self.release.set_commits([{ 'id': 'a' * 40, 'repository': self.repo.name, 'author_email': '*****@*****.**', 'author_name': 'Bob', 'message': 'i fixed a bug', 'patch_set': [ { 'path': 'src/main/java/io/sentry/example/Application.java', 'type': 'M', }, ] }]) result = get_serialized_event_file_committers(self.project, event) assert len(result) == 1 assert 'commits' in result[0] assert len(result[0]['commits']) == 1 assert result[0]['commits'][0]['id'] == 'a' * 40
def test_not_matching(self): event = self.store_event( data={ "message": "Kaboom!", "platform": "python", "stacktrace": { "frames": [ { "function": "handle_set_commits", "abs_path": "/usr/src/sentry/src/sentry/tasks.py", "module": "sentry.tasks", "in_app": True, "lineno": 30, "filename": "sentry/tasks.py", }, { "function": "set_commits", "abs_path": "/usr/src/sentry/src/sentry/models/release.py", "module": "sentry.models.release", "in_app": True, "lineno": 39, "filename": "sentry/models/release.py", }, ] }, "tags": { "sentry:release": self.release.version }, }, project_id=self.project.id, ) self.release.set_commits([{ "id": "a" * 40, "repository": self.repo.name, "author_email": "*****@*****.**", "author_name": "Bob", "message": "i fixed a bug", "patch_set": [{ "path": "some/other/path.py", "type": "M" }], }]) result = get_serialized_event_file_committers(self.project, event) assert len(result) == 0
def get(self, request, 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 """ use_snuba = options.get('snuba.events-queries.enabled') event_cls = event_cls = SnubaEvent if use_snuba else Event event = event_cls.objects.from_event_id(event_id, project.id) if event is None: return Response({'detail': 'Event not found'}, status=404) # populate event data Event.objects.bind_nodes([event], 'data') try: committers = get_serialized_event_file_committers( project, event, frame_limit=int(request.GET.get('frameLimit', 25)), ) except Release.DoesNotExist: return Response({'detail': 'Release not found'}, status=404) except Commit.DoesNotExist: return Response({'detail': 'No Commits found for Release'}, status=404) # XXX(dcramer): this data is unused, so lets not bother returning it for now # serialize the commit objects # serialized_annotated_frames = [ # { # 'frame': frame['frame'], # 'commits': serialize(frame['commits']) # } for frame in annotated_frames # ] data = { 'committers': committers, # 'annotatedFrames': serialized_annotated_frames } return Response(data)
def test_matching(self): event = self.create_event( group=self.group, message='Kaboom!', platform='python', stacktrace={ 'frames': [{ "function": "handle_set_commits", "abs_path": "/usr/src/sentry/src/sentry/tasks.py", "module": "sentry.tasks", "in_app": True, "lineno": 30, "filename": "sentry/tasks.py", }, { "function": "set_commits", "abs_path": "/usr/src/sentry/src/sentry/models/release.py", "module": "sentry.models.release", "in_app": True, "lineno": 39, "filename": "sentry/models/release.py", }] }) self.release.set_commits([{ 'id': 'a' * 40, 'repository': self.repo.name, 'author_email': '*****@*****.**', 'author_name': 'Bob', 'message': 'i fixed a bug', 'patch_set': [ { 'path': 'src/sentry/models/release.py', 'type': 'M', }, ] }]) result = get_serialized_event_file_committers(self.project, event) assert len(result) == 1 assert 'commits' in result[0] assert len(result[0]['commits']) == 1 assert result[0]['commits'][0]['id'] == 'a' * 40
def get(self, request, 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 """ event = eventstore.get_event_by_id(project.id, event_id) if event is None: return Response({"detail": "Event not found"}, status=404) # populate event data if not options.get("eventstore.use-nodestore"): event.bind_node_data() try: committers = get_serialized_event_file_committers( project, event, frame_limit=int(request.GET.get("frameLimit", 25))) except Release.DoesNotExist: return Response({"detail": "Release not found"}, status=404) except Commit.DoesNotExist: return Response({"detail": "No Commits found for Release"}, status=404) # XXX(dcramer): this data is unused, so lets not bother returning it for now # serialize the commit objects # serialized_annotated_frames = [ # { # 'frame': frame['frame'], # 'commits': serialize(frame['commits']) # } for frame in annotated_frames # ] data = { "committers": committers, # 'annotatedFrames': serialized_annotated_frames } return Response(data)
def test_matching_case_insensitive(self): event = self.store_event( data={ "message": "Kaboom!", "platform": "csp", "stacktrace": { "frames": [{ "function": "roar", "abs_path": "/usr/src/app/TigerMachine.cpp", "module": "", "in_app": True, "lineno": 30, "filename": "app/TigerMachine.cpp", }] }, "tags": { "sentry:release": self.release.version }, }, project_id=self.project.id, ) self.release.set_commits([{ "id": "a" * 40, "repository": self.repo.name, "author_email": "*****@*****.**", "author_name": "Bob", "message": "i fixed a bug", "patch_set": [{ "path": "app/tigermachine.cpp", "type": "M" }], }]) GroupRelease.objects.create(group_id=event.group.id, project_id=self.project.id, release_id=self.release.id) result = get_serialized_event_file_committers(self.project, event) assert len(result) == 1 assert "commits" in result[0] assert len(result[0]["commits"]) == 1 assert result[0]["commits"][0]["id"] == "a" * 40
def test_matching_case_insensitive(self): event = self.create_event(group=self.group, message='Kaboom!', platform='cpp', stacktrace={ 'frames': [{ "function": "roar", "abs_path": "/usr/src/app/TigerMachine.cpp", "module": "", "in_app": True, "lineno": 30, "filename": "app/TigerMachine.cpp", }] }) self.release.set_commits([{ 'id': 'a' * 40, 'repository': self.repo.name, 'author_email': '*****@*****.**', 'author_name': 'Bob', 'message': 'i fixed a bug', 'patch_set': [ { 'path': 'app/tigermachine.cpp', 'type': 'M', }, ] }]) result = get_serialized_event_file_committers(self.project, event) assert len(result) == 1 assert 'commits' in result[0] assert len(result[0]['commits']) == 1 assert result[0]['commits'][0]['id'] == 'a' * 40
def test_matching(self): event = self.create_event( group=self.group, message="Kaboom!", platform="python", stacktrace={ "frames": [ { "function": "handle_set_commits", "abs_path": "/usr/src/sentry/src/sentry/tasks.py", "module": "sentry.tasks", "in_app": True, "lineno": 30, "filename": "sentry/tasks.py", }, { "function": "set_commits", "abs_path": "/usr/src/sentry/src/sentry/models/release.py", "module": "sentry.models.release", "in_app": True, "lineno": 39, "filename": "sentry/models/release.py", }, ] }, ) self.release.set_commits( [ { "id": "a" * 40, "repository": self.repo.name, "author_email": "*****@*****.**", "author_name": "Bob", "message": "i fixed a bug", "patch_set": [{"path": "src/sentry/models/release.py", "type": "M"}], } ] ) result = get_serialized_event_file_committers(self.project, event) assert len(result) == 1 assert "commits" in result[0] assert len(result[0]["commits"]) == 1 assert result[0]["commits"][0]["id"] == "a" * 40
def test_no_matching_user(self): self.set_release_commits("*****@*****.**") result = get_serialized_event_file_committers(self.project, self.event) assert len(result) == 1 assert "commits" in result[0] assert len(result[0]["commits"]) == 1 assert result[0]["commits"][0]["id"] == "a" * 40 assert not GroupOwner.objects.filter(group=self.event.group).exists() event_frames = get_frame_paths(self.event.data) process_suspect_commits( event_id=self.event.event_id, event_platform=self.event.platform, event_frames=event_frames, group_id=self.event.group_id, project_id=self.event.project_id, ) assert not GroupOwner.objects.filter(group=self.event.group).exists()
def get_commits(project: Project, event: Event) -> Sequence[Mapping[str, Any]]: # lets identify possibly suspect commits and owners commits: MutableMapping[int, Mapping[str, Any]] = {} try: committers = get_serialized_event_file_committers(project, event) except (Commit.DoesNotExist, Release.DoesNotExist): pass except Exception as exc: logging.exception(str(exc)) else: for committer in committers: for commit in committer["commits"]: if commit["id"] not in commits: commit_data = commit.copy() commit_data["shortId"] = commit_data["id"][:7] commit_data["author"] = committer["author"] commit_data["subject"] = commit_data["message"].split( "\n", 1)[0] commits[commit["id"]] = commit_data return sorted(commits.values(), key=lambda x: float(x["score"]), reverse=True)
def test_matching_case_insensitive(self): event = self.create_event( group=self.group, message="Kaboom!", platform="cpp", stacktrace={ "frames": [ { "function": "roar", "abs_path": "/usr/src/app/TigerMachine.cpp", "module": "", "in_app": True, "lineno": 30, "filename": "app/TigerMachine.cpp", } ] }, ) self.release.set_commits( [ { "id": "a" * 40, "repository": self.repo.name, "author_email": "*****@*****.**", "author_name": "Bob", "message": "i fixed a bug", "patch_set": [{"path": "app/tigermachine.cpp", "type": "M"}], } ] ) result = get_serialized_event_file_committers(self.project, event) assert len(result) == 1 assert "commits" in result[0] assert len(result[0]["commits"]) == 1 assert result[0]["commits"][0]["id"] == "a" * 40
def notify(self, notification, target_type, target_identifier=None, **kwargs): metrics.incr("mail_adapter.notify") event = notification.event environment = event.get_tag("environment") group = event.group project = group.project org = group.organization logger.info( "mail.adapter.notify", extra={ "target_type": target_type.value, "target_identifier": target_identifier, "group": group.id, "project_id": project.id, }, ) subject = event.get_email_subject() query_params = {"referrer": "alert_email"} if environment: query_params["environment"] = environment link = group.get_absolute_url(params=query_params) template = "sentry/emails/error.txt" html_template = "sentry/emails/error.html" rules = [] for rule in notification.rules: rule_link = "/settings/%s/projects/%s/alerts/rules/%s/" % ( org.slug, project.slug, rule.id, ) rules.append((rule.label, rule_link)) enhanced_privacy = org.flags.enhanced_privacy # lets identify possibly suspect commits and owners commits = {} try: committers = get_serialized_event_file_committers(project, event) except (Commit.DoesNotExist, Release.DoesNotExist): pass except Exception as exc: logging.exception(six.text_type(exc)) else: for committer in committers: for commit in committer["commits"]: if commit["id"] not in commits: commit_data = commit.copy() commit_data["shortId"] = commit_data["id"][:7] commit_data["author"] = committer["author"] commit_data["subject"] = commit_data["message"].split( "\n", 1)[0] commits[commit["id"]] = commit_data project_plugins = plugins.for_project(project, version=1) organization_integrations = Integration.objects.filter( organizations=org).first() has_integrations = bool(project_plugins or organization_integrations) context = { "project_label": project.get_full_name(), "group": group, "event": event, "link": link, "rules": rules, "has_integrations": has_integrations, "enhanced_privacy": enhanced_privacy, "commits": sorted(commits.values(), key=lambda x: x["score"], reverse=True), "environment": environment, } # if the organization has enabled enhanced privacy controls we dont send # data which may show PII or source code if not enhanced_privacy: interface_list = [] for interface in six.itervalues(event.interfaces): body = interface.to_email_html(event) if not body: continue text_body = interface.to_string(event) interface_list.append( (interface.get_title(), mark_safe(body), text_body)) context.update({"tags": event.tags, "interfaces": interface_list}) headers = { "X-Sentry-Logger": group.logger, "X-Sentry-Logger-Level": group.get_level_display(), "X-Sentry-Project": project.slug, "X-Sentry-Reply-To": group_id_to_email(group.id), } for user_id in self.get_send_to( project=project, target_type=target_type, target_identifier=target_identifier, event=event, ): logger.info( "mail.adapter.notify.mail_user", extra={ "target_type": target_type, "target_identifier": target_identifier, "group": group.id, "project_id": project.id, "user_id": user_id, }, ) self.add_unsubscribe_link(context, user_id, project, "alert_email") self._send_mail( subject=subject, template=template, html_template=html_template, project=project, reference=group, headers=headers, type="notify.error", context=context, send_to=[user_id], )
def notify(self, notification): from sentry.models import Commit, Release event = notification.event environment = event.get_tag('environment') group = event.group project = group.project org = group.organization subject = event.get_email_subject() query_params = {'referrer': 'alert_email'} if environment: query_params['environment'] = environment link = group.get_absolute_url(params=query_params) template = 'sentry/emails/error.txt' html_template = 'sentry/emails/error.html' rules = [] for rule in notification.rules: rule_link = '/settings/%s/projects/%s/alerts/rules/%s/' % ( org.slug, project.slug, rule.id) rules.append((rule.label, rule_link)) enhanced_privacy = org.flags.enhanced_privacy # lets identify possibly suspect commits and owners commits = {} try: committers = get_serialized_event_file_committers(project, event) except (Commit.DoesNotExist, Release.DoesNotExist): pass except Exception as exc: logging.exception(six.text_type(exc)) else: for committer in committers: for commit in committer['commits']: if commit['id'] not in commits: commit_data = commit.copy() commit_data['shortId'] = commit_data['id'][:7] commit_data['author'] = committer['author'] commit_data['subject'] = commit_data['message'].split( '\n', 1)[0] commits[commit['id']] = commit_data context = { 'project_label': project.get_full_name(), 'group': group, 'event': event, 'link': link, 'rules': rules, 'enhanced_privacy': enhanced_privacy, 'commits': sorted(commits.values(), key=lambda x: x['score'], reverse=True), 'environment': environment } # if the organization has enabled enhanced privacy controls we dont send # data which may show PII or source code if not enhanced_privacy: interface_list = [] for interface in six.itervalues(event.interfaces): body = interface.to_email_html(event) if not body: continue text_body = interface.to_string(event) interface_list.append( (interface.get_title(), mark_safe(body), text_body)) context.update({ 'tags': event.tags, 'interfaces': interface_list, }) headers = { 'X-Sentry-Logger': group.logger, 'X-Sentry-Logger-Level': group.get_level_display(), 'X-Sentry-Project': project.slug, 'X-Sentry-Reply-To': group_id_to_email(group.id), } for user_id in self.get_send_to(project=project, event=event): self.add_unsubscribe_link(context, user_id, project, 'alert_email') self._send_mail( subject=subject, template=template, html_template=html_template, project=project, reference=group, headers=headers, type='notify.error', context=context, send_to=[user_id], )