Skip to content

Commit

Permalink
Merge pull request collective#6 from krissik/group-errors
Browse files Browse the repository at this point in the history
Send exceptions to sentry directly w/o using logging
  • Loading branch information
zopyx authored Jul 8, 2020
2 parents d00ca97 + 4a7d7a9 commit 7a88dc1
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 47 deletions.
8 changes: 5 additions & 3 deletions README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ additional tag `project` can be configured (optional) if you set the
environment variable `SENTRY_PROJECT`. This allows you introduce an additional
tag for filtering, if needed.

Another option for filtering is `SENTRY_ENVIRONMENT`, useful to differentiate
between e.g. staging vs production
(https://docs.sentry.io/enriching-error-data/environments/?platform=python).

Set `SENTRY_ENVIRONMENT` to differentiate between environments e.g. staging vs production
(https://docs.sentry.io/enriching-error-data/environments/)

Set `SENTRY_RELEASE` to sent release information to sentry. (https://docs.sentry.io/workflow/releases/)


Optional activation
Expand Down
2 changes: 1 addition & 1 deletion collective/sentry/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
xmlns:plone="http://namespaces.plone.org/plone"
>

<subscriber handler=".error_handler.dummy"/>
<subscriber handler=".error_handler.errorRaisedSubscriber"/>
</configure>
134 changes: 91 additions & 43 deletions collective/sentry/error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@
# Integration of Zope (4) with Sentry
# The code below is heavily based on the raven.contrib. zope module

import os
import logging
import os
import sys
import traceback

import sentry_sdk
import sentry_sdk.utils as sentry_utils

from AccessControl.users import nobody
from AccessControl.SecurityManagement import getSecurityManager
from App.config import getConfiguration
from sentry_sdk.integrations.logging import ignore_logger
from zope.component import adapter
from zope.globalrequest import getRequest
from AccessControl.users import nobody
from ZPublisher.interfaces import IPubFailure
from ZPublisher.HTTPRequest import _filterPasswordFields
from ZPublisher.interfaces import IPubFailure


sentry_dsn = os.environ.get("SENTRY_DSN")

Expand All @@ -26,15 +31,49 @@
sentry_max_length = os.environ.get("SENTRY_MAX_LENGTH")


def _before_send(event, hint):
"""
Inject Plone/Zope specific information (based on raven.contrib.zope)
"""
def _get_user_from_request(request):
user = request.get("AUTHENTICATED_USER", None)

request = getRequest()
if not request:
return event
if(user is None):
user = getSecurityManager().getUser()

if user is not None and user != nobody:
user_dict = {
"id": user.getId(),
"email": user.getProperty("email") or "",
}
else:
user_dict = {}

return user_dict

def _get_other_from_request(request):
other = {}
for k, v in _filterPasswordFields(request.other.items()):
if k in ('PARENTS', 'RESPONSE'):
continue
other[k] = repr(v)
return other

def _get_lazyitems_from_request(request):
lazy_items = {}
for k, v in _filterPasswordFields(request._lazies.items()):
lazy_items[k] = repr(v)
return lazy_items

def _get_cookies_from_request(request):
cookies = {}
for k, v in _filterPasswordFields(request.cookies.items()):
cookies[k] = repr(v)
return cookies

def _get_form_from_request(request):
form = {}
for k, v in _filterPasswordFields(request.form.items()):
form[k] = repr(v)
return form

def _get_request_from_request(request):
# ensure that all header key-value pairs are strings
headers = dict()
for k, v in request.environ.items():
Expand All @@ -58,36 +97,34 @@ def _before_send(event, hint):
if "QUERY_STRING" in http["headers"]:
http["query_string"] = http["headers"]["QUERY_STRING"]

event["extra"]["request"] = http

event["extra"]["form"] = {}
event["extra"]["other"] = {}
event["extra"]["cookies"] = {}
event["extra"]["lazy items"] = {}
return http

for k, v in _filterPasswordFields(request.form.items()):
event["extra"]["form"][k] = repr(v)

for k, v in _filterPasswordFields(request.cookies.items()):
event["extra"]["cookies"][k] = repr(v)

for k, v in _filterPasswordFields(request._lazies.items()):
event["extra"]["lazy items"][k] = repr(v)

for k, v in _filterPasswordFields(request.other.items()):
if k in ('PARENTS', 'RESPONSE'):
continue
event["extra"]["other"][k] = repr(v)
def _before_send(event, hint):
"""
Inject Plone/Zope specific information (based on raven.contrib.zope)
"""
request = getRequest()

user = request.get("AUTHENTICATED_USER", None)
if user is not None and user != nobody:
user_dict = {
"id": user.getId(),
"email": user.getProperty("email") or "",
}
else:
user_dict = {}
event["extra"]["user"] = user_dict
if request:
# We have no request if event is captured by errorRaisedSubscriber (see below)
# so extra information must be set there.
# If the event is send by pythons logging module we set extra info here.
if not "other" in event["extra"]:
event["extra"]["other"] = _get_other_from_request(request)
if not "lazy items" in event["extra"]:
event["extra"]["lazy items"] = _get_lazyitems_from_request(request)
if not "cookies" in event["extra"]:
event["extra"]["cookies"] = _get_cookies_from_request(request)
if not "form" in event["extra"]:
event["extra"]["form"] = _get_form_from_request(request)
if not "request" in event["extra"]:
event["extra"]["request"] = _get_request_from_request(request)
user_info = _get_user_from_request(request)
if not "user" in event["extra"]:
event["extra"]["user"] = user_info
if not "user" in event:
event["user"] = user_info

return event

Expand Down Expand Up @@ -120,7 +157,8 @@ def before_send(event, hint):
sentry_dsn,
max_breadcrumbs=50,
before_send=before_send,
debug=False,
attach_stacktrace=True,
debug=False
environment=sentry_environment,
)

Expand All @@ -136,10 +174,20 @@ def before_send(event, hint):
scope.set_tag("project", sentry_project)

logging.info("Sentry integration enabled")
ignore_logger("Zope.SiteErrorLog")


# fake registration in order to import the file properly
# for the sentry_skd.init() call
@adapter(IPubFailure)
def dummy(event):
pass
def errorRaisedSubscriber(event):
with sentry_sdk.push_scope() as scope:
scope.set_extra("other", _get_other_from_request(event.request))
scope.set_extra("lazy items", _get_lazyitems_from_request(event.request))
scope.set_extra("cookies", _get_cookies_from_request(event.request))
scope.set_extra("form", _get_form_from_request(event.request))
scope.set_extra("request", _get_request_from_request(event.request))
user_info = _get_user_from_request(event.request)
scope.set_extra("user", user_info)
if user_info and 'id' in user_info:
scope.user = user_info

sentry_sdk.capture_exception(sys.exc_info())

0 comments on commit 7a88dc1

Please sign in to comment.