webhooks/sentry: Support integration configured as webhook.

Sentry allows adding simple webhooks without going through the process
of creating an Internal Integration in Sentry's Integration
Platform[1] (which our docs recommend).

The payload from sent from such a (simple) webhook integration is
slightly different from the payload sent by an Internal Integration
webhook. This commit tries to wrangle this payload into a form that is
usable by our webhook handler to send a notification message.

[1]: https://sentry.io/integration-platform/
This commit is contained in:
Puneeth Chaganti
2020-10-23 11:50:24 +05:30
committed by Alex Vandiver
parent 47228f3a95
commit 358f1f9ba7
4 changed files with 253 additions and 0 deletions

View File

@@ -21,6 +21,12 @@ you can scroll down to **Webhooks** on the same page and check the
box that says **issues**. Make sure that you set up the permissions
so that the integration will visible to the right people.
**NOTE:** Zulip also supports configuring this as a webhook in Sentry
— which, while easier to configure (Navigate to **Settings > Integrations
> WebHooks**) may not include the full breadth of event types. For instance,
some events, like issue assignments or issues being resolved, will not trigger
notifications with this configuration.
4. Once you've saved the internal integration, go to you're project's
settings (**settings** > **Projects** > Select the project). Once
there go to **Alerts** and click on the **New Alert Rule** button to

View File

@@ -0,0 +1,199 @@
{
"project_name": "live",
"message": "",
"id": "1972208801",
"culprit": "__main__ in <module>",
"project_slug": "foo-live",
"url": "https://sentry.io/organizations/bar-foundation/issues/1972208801/?referrer=webhooks_plugin",
"level": "error",
"triggering_rules": [
"Send a notification for new events"
],
"event": {
"extra": {
"sys.argv": [
"data/trigger-exception.py"
]
},
"_ref_version": 2,
"_ref": 218288,
"id": "c916dccfd58e41dcabaebef0091f0736",
"_metrics": {
"bytes.ingested.event": 2229,
"bytes.stored.event": 3021
},
"culprit": "__main__ in <module>",
"title": "ValueError: new sentry error.",
"event_id": "c916dccfd58e41dcabaebef0091f0736",
"grouping_config": {
"enhancements": "eJybzDhxY05qemJypZWRgaGlroGxrqHRBABbEwcC",
"id": "legacy:2019-03-12"
},
"platform": "python",
"version": "7",
"location": "trigger-exception.py",
"logger": "",
"type": "error",
"metadata": {
"function": "<module>",
"type": "ValueError",
"value": "new sentry error.",
"filename": "trigger-exception.py"
},
"tags": [
[
"level",
"error"
],
[
"runtime",
"CPython 3.8.5"
],
[
"runtime.name",
"CPython"
],
[
"server_name",
"enterprise"
]
],
"timestamp": 1603322711.147672,
"fingerprint": [
"{{ default }}"
],
"hashes": [
"05eeb8cc2a9103790797a56728cfa1e8"
],
"sdk": {
"version": "0.19.1",
"name": "sentry.python",
"packages": [
{
"version": "0.19.1",
"name": "pypi:sentry-sdk"
}
],
"integrations": [
"argv",
"atexit",
"dedupe",
"excepthook",
"logging",
"modules",
"stdlib",
"threading"
]
},
"received": 1603322712.317751,
"exception": {
"values": [
{
"stacktrace": {
"frames": [
{
"function": "<module>",
"abs_path": "/home/user/software/bar/bar-foo/data/trigger-exception.py",
"pre_context": [
"",
"",
"if __name__ == \"__main__\":",
" sentry_sdk.init(dsn=DSN_SECRET)",
" try:"
],
"post_context": [
" except Exception as e:",
" sentry_sdk.capture_exception(e)"
],
"vars": {
"__spec__": "None",
"__builtins__": "<module 'builtins' (built-in)>",
"__annotations__": null,
"__file__": "'data/trigger-exception.py'",
"__loader__": "<_frozen_importlib_external.SourceFileLoader object at 0x7f80a18acc70>",
"__cached__": "None",
"__name__": "'__main__'",
"__package__": "None",
"__doc__": "None",
"sentry_sdk": "<module 'sentry_sdk' from '/home/user/.virtualenvs/sentry-test/lib/python3.8/site-packages/sentry_sdk/__init__.py'>"
},
"module": "__main__",
"filename": "trigger-exception.py",
"lineno": 10,
"in_app": false,
"data": {
"orig_in_app": 1
},
"context_line": " raise ValueError(\"new sentry error.\")"
}
]
},
"type": "ValueError",
"value": "new sentry error."
}
]
},
"_meta": {
"exception": {
"values": {
"0": {
"stacktrace": {
"frames": {
"0": {
"vars": {
"": {
"len": 12
}
}
}
}
}
}
}
}
},
"level": "error",
"contexts": {
"runtime": {
"version": "3.8.5",
"type": "runtime",
"name": "CPython",
"build": "3.8.5 (default, Jul 28 2020, 12:59:40) \n[GCC 9.3.0]"
}
},
"modules": {
"six": "1.14.0",
"packaging": "20.3",
"pip": "20.0.2",
"pyparsing": "2.4.6",
"html5lib": "1.0.1",
"appdirs": "1.4.3",
"distlib": "0.3.0",
"msgpack": "0.6.2",
"sentry-sdk": "0.19.1",
"pytoml": "0.1.21",
"ipaddr": "2.2.0",
"webencodings": "0.5.1",
"progress": "1.5",
"certifi": "2019.11.28",
"distro": "1.4.0",
"wheel": "0.34.2",
"pkg-resources": "0.0.0",
"urllib3": "1.25.8",
"retrying": "1.3.3",
"colorama": "0.4.3",
"cachecontrol": "0.12.6",
"lockfile": "0.12.2",
"pep517": "0.8.2",
"contextlib2": "0.6.0",
"chardet": "3.0.4",
"setuptools": "44.0.0",
"requests": "2.22.0",
"idna": "2.8"
},
"project": 218288,
"key_id": "430951"
},
"project": "foo-live",
"logger": null
}

View File

@@ -82,6 +82,29 @@ Traceback:
```"""
self.check_webhook("event_for_exception_python", expected_topic, expected_message)
def test_webhook_event_for_exception_python(self) -> None:
expected_topic = "ValueError: new sentry error."
expected_message = """
**New exception:** [ValueError: new sentry error.](https://sentry.io/organizations/bar-foundation/issues/1972208801/event/c916dccfd58e41dcabaebef0091f0736/)
```quote
**level:** error
**timestamp:** 2020-10-21 23:25:11
**filename:** trigger-exception.py
```
Traceback:
```python3
if __name__ == "__main__":
sentry_sdk.init(dsn=DSN_SECRET)
try:
---> raise ValueError("new sentry error.")
except Exception as e:
sentry_sdk.capture_exception(e)
```"""
self.check_webhook("webhook_event_for_exception_python", expected_topic, expected_message)
def test_event_for_message_golang(self) -> None:
expected_topic = "A test message event from golang."
expected_message = """

View File

@@ -1,5 +1,7 @@
import logging
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from urllib.parse import urljoin
from django.http import HttpRequest, HttpResponse
@@ -220,12 +222,35 @@ def handle_deprecated_payload(payload: Dict[str, Any]) -> Tuple[str, str]:
return (subject, body)
def transform_webhook_payload(payload: Dict[str, Any]) -> Optional[Dict[str, Any]]:
"""Attempt to use webhook payload for the notification.
When the integration is configured as a webhook, instead of being added as
an Internal Integration, the payload is slightly different, but has all the
required information for sending a notification. We transform this payload to
look like the payload from a "properly configured" integration.
"""
event = payload.get('event', {})
# deprecated payloads don't have event_id
event_id = event.get('event_id')
if not event_id:
return None
event_path = f"event/{event_id}/"
event['web_url'] = urljoin(payload['url'], event_path)
event['datetime'] = datetime.fromtimestamp(event['timestamp']).isoformat()
return payload
@webhook_view('Sentry')
@has_request_variables
def api_sentry_webhook(request: HttpRequest, user_profile: UserProfile,
payload: Dict[str, Any] = REQ(argument_type="body")) -> HttpResponse:
data = payload.get("data", None)
if data is None:
data = transform_webhook_payload(payload)
# We currently support two types of payloads: events and issues.
if data:
if "event" in data: