mirror of
https://github.com/zulip/zulip.git
synced 2025-11-02 13:03:29 +00:00
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:
committed by
Alex Vandiver
parent
47228f3a95
commit
358f1f9ba7
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 = """
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user