Skip to content

Commit 31aca4a

Browse files
committed
ref(slack): Add APM to Slack and use use ApiClient
1 parent 6e6ed91 commit 31aca4a

File tree

6 files changed

+97
-41
lines changed

6 files changed

+97
-41
lines changed

src/sentry/api/endpoints/project_rule_details.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@
99
from sentry.integrations.slack import tasks
1010
from sentry.mediators import project_rules
1111
from sentry.models import AuditLogEntryEvent, Rule, RuleStatus
12+
from sentry.web.decorators import transaction_start
1213

1314

1415
class ProjectRuleDetailsEndpoint(ProjectEndpoint):
1516
permission_classes = [ProjectSettingPermission]
1617

18+
@transaction_start("ProjectRuleDetailsEndpoint")
1719
def get(self, request, project, rule_id):
1820
"""
1921
Retrieve a rule
@@ -28,6 +30,7 @@ def get(self, request, project, rule_id):
2830
)
2931
return Response(serialize(rule, request.user))
3032

33+
@transaction_start("ProjectRuleDetailsEndpoint")
3134
def put(self, request, project, rule_id):
3235
"""
3336
Update a rule
@@ -81,6 +84,7 @@ def put(self, request, project, rule_id):
8184

8285
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
8386

87+
@transaction_start("ProjectRuleDetailsEndpoint")
8488
def delete(self, request, project, rule_id):
8589
"""
8690
Delete a rule

src/sentry/api/endpoints/project_rules.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
from sentry.mediators import project_rules
1212
from sentry.models import AuditLogEntryEvent, Rule, RuleStatus
1313
from sentry.signals import alert_rule_created
14+
from sentry.web.decorators import transaction_start
1415

1516

1617
class ProjectRulesEndpoint(ProjectEndpoint):
18+
@transaction_start("ProjectRulesEndpoint")
1719
def get(self, request, project):
1820
"""
1921
List a project's rules
@@ -34,6 +36,7 @@ def get(self, request, project):
3436
on_results=lambda x: serialize(x, request.user),
3537
)
3638

39+
@transaction_start("ProjectRulesEndpoint")
3740
def post(self, request, project):
3841
"""
3942
Create a rule
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from __future__ import absolute_import
2+
3+
import six
4+
5+
from sentry.integrations.client import ApiClient
6+
from sentry.shared_integrations.exceptions import ApiError
7+
from sentry.utils import metrics
8+
9+
SLACK_DATADOG_METRIC = "integrations.slack.http_response"
10+
11+
12+
class SlackClient(ApiClient):
13+
allow_redirects = False
14+
integration_name = "slack"
15+
base_url = "https://slack.com/api"
16+
datadog_prefix = "integrations.slack"
17+
18+
def __init__(self):
19+
super(SlackClient, self).__init__()
20+
21+
def track_response_data(self, code, span, error=None, resp=None):
22+
span.set_http_status(code)
23+
span.set_tag("integration", "slack")
24+
25+
# If Slack gives us back a 200 we still want to check the 'ok' param
26+
if resp:
27+
response = resp.json()
28+
is_ok = response.get("ok")
29+
span.set_tag("ok", is_ok)
30+
31+
# when 'ok' is False, we can add the error we get back as a tag
32+
if not is_ok:
33+
span.set_tag("slack_error", response.get("error"))
34+
35+
metrics.incr(
36+
SLACK_DATADOG_METRIC,
37+
sample_rate=1.0,
38+
tags={"ok": False if is_ok is False else True, "status": code},
39+
)
40+
41+
extra = {
42+
self.integration_type: self.name,
43+
"status_string": six.text_type(code),
44+
"error": six.text_type(error)[:256] if error else None,
45+
}
46+
extra.update(getattr(self, "logging_context", None) or {})
47+
self.logger.info(u"%s.http_response" % (self.integration_type), extra=extra)
48+
49+
def request(self, method, path, headers=None, data=None, params=None, timeout=None):
50+
# TODO(meredith): Slack actually supports json now for the chat.postMessage so we
51+
# can update that so we don't have to pass json=False here
52+
response = self._request(
53+
method, path, headers=headers, data=data, params=params, json=False
54+
)
55+
if not response.json.get("ok"):
56+
raise ApiError(response.get("error", ""))
57+
return response

src/sentry/integrations/slack/notify_action.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
from __future__ import absolute_import
22

3+
import six
4+
35
from django import forms
46
from django.utils.translation import ugettext_lazy as _
57

6-
from sentry import http
78
from sentry.rules.actions.base import EventAction
89
from sentry.utils import metrics, json
910
from sentry.models import Integration
11+
from sentry.shared_integrations.exceptions import ApiError
1012

11-
from .utils import build_group_attachment, get_channel_id, strip_channel_name, track_response_code
13+
from .client import SlackClient
14+
from .utils import build_group_attachment, get_channel_id, strip_channel_name
1215

1316

1417
class SlackNotifyServiceForm(forms.Form):
@@ -114,17 +117,14 @@ def send_notification(event, futures):
114117
"attachments": json.dumps([attachment]),
115118
}
116119

117-
session = http.build_session()
118-
resp = session.post("https://slack.com/api/chat.postMessage", data=payload, timeout=5)
119-
status_code = resp.status_code
120-
response = resp.json()
121-
track_response_code(status_code, response.get("ok"))
122-
resp.raise_for_status()
123-
if not response.get("ok"):
120+
client = SlackClient()
121+
try:
122+
client.post("/chat.postMessage", data=payload, timeout=5)
123+
except ApiError as e:
124124
self.logger.info(
125125
"rule.fail.slack_post",
126126
extra={
127-
"error": response.get("error"),
127+
"error": six.text_type(e),
128128
"project_id": event.project_id,
129129
"event_id": event.event_id,
130130
},

src/sentry/integrations/slack/utils.py

Lines changed: 21 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22

33
import logging
44
import time
5+
import six
56
from datetime import timedelta
67

78
from django.core.cache import cache
89
from django.core.urlresolvers import reverse
910

10-
from sentry import http
1111
from sentry import tagstore
1212
from sentry.api.fields.actor import Actor
1313
from sentry.incidents.logic import get_incident_aggregates
1414
from sentry.incidents.models import IncidentStatus, IncidentTrigger
1515
from sentry.snuba.models import QueryAggregations
16-
from sentry.utils import metrics, json
16+
from sentry.utils import json
1717
from sentry.utils.assets import get_asset_url
1818
from sentry.utils.dates import to_timestamp
1919
from sentry.utils.http import absolute_uri
@@ -29,6 +29,9 @@
2929
ReleaseProject,
3030
)
3131

32+
from sentry.shared_integrations.exceptions import ApiError
33+
from .client import SlackClient
34+
3235
logger = logging.getLogger("sentry.integrations.slack")
3336

3437
# Attachment colors used for issues with no actions take
@@ -46,15 +49,6 @@
4649
strip_channel_chars = "".join([MEMBER_PREFIX, CHANNEL_PREFIX])
4750
SLACK_DEFAULT_TIMEOUT = 10
4851
QUERY_AGGREGATION_DISPLAY = ["events", "users affected"]
49-
SLACK_DATADOG_METRIC = "integrations.slack.http_response"
50-
51-
52-
def track_response_code(status_code, is_ok):
53-
metrics.incr(
54-
SLACK_DATADOG_METRIC,
55-
sample_rate=1.0,
56-
tags={"ok": False if is_ok is False else True, "status": status_code},
57-
)
5852

5953

6054
def format_actor_option(actor):
@@ -409,21 +403,19 @@ def get_channel_id_with_timeout(integration, name, timeout):
409403
payload = dict(token_payload, **{"exclude_archived": False, "exclude_members": True})
410404

411405
time_to_quit = time.time() + timeout
412-
session = http.build_session()
406+
407+
client = SlackClient()
408+
# session = http.build_session()
413409
for list_type, result_name, prefix in LIST_TYPES:
414410
cursor = ""
415411
while True:
416-
items = session.get(
417-
"https://slack.com/api/%s.list" % list_type,
412+
endpoint = "/%s.list" % list_type
413+
try:
418414
# Slack limits the response of `<list_type>.list` to 1000 channels
419-
params=dict(payload, cursor=cursor, limit=1000),
420-
)
421-
status_code = items.status_code
422-
items = items.json()
423-
track_response_code(status_code, items.get("ok"))
424-
if not items.get("ok"):
415+
items = client.get(endpoint, params=dict(payload, cursor=cursor, limit=1000))
416+
except ApiError as e:
425417
logger.info(
426-
"rule.slack.%s_list_failed" % list_type, extra={"error": items.get("error")}
418+
"rule.slack.%s_list_failed" % list_type, extra={"error": six.text_type(e)}
427419
)
428420
return (prefix, None, False)
429421

@@ -451,11 +443,11 @@ def send_incident_alert_notification(action, incident):
451443
"attachments": json.dumps([attachment]),
452444
}
453445

454-
session = http.build_session()
455-
resp = session.post("https://slack.com/api/chat.postMessage", data=payload, timeout=5)
456-
status_code = resp.status_code
457-
response = resp.json()
458-
track_response_code(status_code, response.get("ok"))
459-
resp.raise_for_status()
460-
if not response.get("ok"):
461-
logger.info("rule.fail.slack_post", extra={"error": response.get("error")})
446+
client = SlackClient()
447+
448+
try:
449+
client.post("/chat.postMessage", data=payload, timeout=5)
450+
except ApiError as e:
451+
logger.info(
452+
"rule.fail.slack_post", extra={"error": six.text_type(e)},
453+
)

src/sentry/shared_integrations/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ def name(cls):
148148
def get_cache_prefix(self):
149149
return u"%s.%s.client:" % (self.integration_type, self.name)
150150

151-
def track_response_data(self, code, span, error=None):
151+
def track_response_data(self, code, span, error=None, resp=None):
152152
metrics.incr(
153153
u"%s.http_response" % (self.datadog_prefix),
154154
sample_rate=1.0,
@@ -242,7 +242,7 @@ def _request(
242242
self.track_response_data(resp.status_code, span, e)
243243
raise ApiError.from_response(resp)
244244

245-
self.track_response_data(resp.status_code, span)
245+
self.track_response_data(resp.status_code, span, None, resp)
246246

247247
if resp.status_code == 204:
248248
return {}

0 commit comments

Comments
 (0)