Skip to content

Commit 2114b7a

Browse files
committed
feat: integration config draft
1 parent 4bf150b commit 2114b7a

File tree

8 files changed

+96
-28
lines changed

8 files changed

+96
-28
lines changed

sentry_sdk/client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@ def __init__(self, dsn=None, *args, **kwargs):
3030
self._transport = Transport(dsn)
3131
self._transport.start()
3232

33+
for integration in options.get("integrations", None) or ():
34+
assert integration.identifier, "identifier of integration must be set"
35+
assert isinstance(integration.identifier, str), "identifier of integration must be a string"
36+
integration.install(self)
37+
3338
@property
3439
def dsn(self):
3540
"""The DSN that created this event."""

sentry_sdk/consts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"environment": None,
1010
"server_name": DEFAULT_SERVER_NAME,
1111
"drain_timeout": 2.0,
12+
"integrations": []
1213
}
1314

1415
SDK_INFO = {"name": "sentry-python", "version": VERSION}

sentry_sdk/integrations/__init__.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class Integration(object):
2+
"""
3+
A interface with an install hook for integrations to patch themselves
4+
into their environment. Integrations don't really *need* to use this hook
5+
if the idiomatic control flow of some framework mandates otherwise.
6+
"""
7+
8+
identifier = None
9+
10+
def install(self, client):
11+
pass
12+
13+
@classmethod
14+
def parse_environment(cls, environ):
15+
client_options = {}
16+
integration_options = {}
17+
for key, value in environ.items():
18+
if not key.startswith("SENTRY_"):
19+
continue
20+
key = key[len("SENTRY_"):].replace("-","_").lower()
21+
identifier_prefix = "%s_" % cls.identifier
22+
if key.startswith(identifier_prefix):
23+
integration_options[key[len(identifier_prefix):]] = value
24+
else:
25+
client_options[key] = value
26+
27+
return client_options, integration_options

sentry_sdk/integrations/django/__init__.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@
1010
except ImportError:
1111
from django.core.urlresolvers import resolve
1212

13-
from sentry_sdk import get_current_hub, configure_scope, capture_exception
13+
from sentry_sdk import get_current_hub, configure_scope, capture_exception, init
1414
from .._wsgi import RequestExtractor
15+
from .. import Integration
1516

1617

1718
try:
@@ -108,7 +109,16 @@ def _install():
108109
with _installer_lock:
109110
if _installed:
110111
return
111-
_install_impl()
112+
113+
client_options, integration_options = \
114+
DjangoIntegration.parse_environment(dict(
115+
(key, getattr(settings, key)) for key in dir(settings)
116+
))
117+
118+
client_options.setdefault("integrations", []) \
119+
.append(DjangoIntegration(**integration_options))
120+
121+
init(**client_options)
112122
_installed = True
113123

114124

@@ -120,17 +130,24 @@ def _install_impl():
120130
middleware_attr = "MIDDLEWARE_CLASSES"
121131

122132
# make sure to get an empty tuple when attr is None
123-
middleware = getattr(settings, middleware_attr, ()) or ()
133+
middleware = list(getattr(settings, middleware_attr, ()) or ())
124134
conflicts = set(CONFLICTING_MIDDLEWARE).intersection(set(middleware))
125135
if conflicts:
126136
raise RuntimeError("Other sentry-middleware already registered: %s" % conflicts)
127137

128-
setattr(settings, middleware_attr, [MIDDLEWARE_NAME] + list(middleware))
138+
setattr(settings, middleware_attr, [MIDDLEWARE_NAME] + middleware)
129139

130140
signals.request_finished.connect(_request_finished)
131141
signals.got_request_exception.connect(_got_request_exception)
132142

133143

144+
class DjangoIntegration(Integration):
145+
identifier = 'django'
146+
147+
def install(self, client):
148+
_install_impl()
149+
150+
134151
try:
135152
# Django >= 1.7
136153
from django.apps import AppConfig

sentry_sdk/integrations/flask.py

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

3-
from sentry_sdk import capture_exception, configure_scope, get_current_hub
3+
from sentry_sdk import capture_exception, configure_scope, get_current_hub, init
44
from ._wsgi import RequestExtractor
5+
from . import Integration
56

67
try:
78
from flask_login import current_user
@@ -13,7 +14,7 @@
1314

1415

1516
class FlaskSentry(object):
16-
def __init__(self, app=None, **options):
17+
def __init__(self, app=None):
1718
self.app = app
1819
if app is not None:
1920
self.init_app(app, **options)
@@ -22,18 +23,33 @@ def init_app(self, app, setup_logger=True):
2223
if not hasattr(app, "extensions"):
2324
app.extensions = {}
2425
elif app.extensions.get(__name__, None):
25-
raise RuntimeError("Sentry registration is already registered")
26+
raise RuntimeError("Sentry extension is already registered")
2627
app.extensions[__name__] = True
2728

28-
appcontext_pushed.connect(self._push_appctx, sender=app)
29-
app.teardown_appcontext(self._pop_appctx)
30-
got_request_exception.connect(self._capture_exception, sender=app)
31-
app.before_request(self._before_request)
29+
client_options, integration_options = \
30+
FlaskIntegration.parse_environment(app.config)
31+
integration = FlaskIntegration(app, **integration_options)
32+
client_options.setdefault('integrations', []).append(integration)
33+
init(**client_options)
3234

33-
if setup_logger:
34-
from .logging import HANDLER
3535

36-
app.logger.addHandler(HANDLER)
36+
class FlaskIntegration(Integration):
37+
identifier = "flask"
38+
39+
def __init__(self, app, setup_logger=True):
40+
self._app = app
41+
self._setup_logger = setup_logger
42+
43+
def install(self, client=None):
44+
appcontext_pushed.connect(self._push_appctx, sender=self._app)
45+
self._app.teardown_appcontext(self._pop_appctx)
46+
got_request_exception.connect(self._capture_exception,
47+
sender=self._app)
48+
self._app.before_request(self._before_request)
49+
50+
if self._setup_logger:
51+
from .logging import HANDLER
52+
self._app.logger.addHandler(HANDLER)
3753

3854
def _push_appctx(self, *args, **kwargs):
3955
get_current_hub().push_scope()

sentry_sdk/transport.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def _make_pool():
3030

3131

3232
def send_event(event, auth):
33-
body = zlib.compress(json.dumps(event.get_json()).encode("utf-8"))
33+
body = zlib.compress(json.dumps(event).encode("utf-8"))
3434
response = _pool.request(
3535
"POST",
3636
auth.store_api_url,

tests/integrations/conftest.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,6 @@
55
from sentry_sdk.client import Client, Transport
66

77

8-
class TestClient(Client):
9-
def __init__(self):
10-
pass
11-
12-
dsn = "LOL"
13-
options = dict(DEFAULT_OPTIONS)
14-
_transport = None
15-
16-
178
class TestTransport(Transport):
189
def __init__(self):
1910
pass
@@ -24,11 +15,20 @@ def start(self):
2415
def close(self):
2516
pass
2617

18+
def capture_event(self, event):
19+
pass
20+
21+
dsn = 'LOL'
2722

28-
test_client = TestClient()
2923

30-
sentry_sdk.init()
31-
sentry_sdk.get_current_hub().bind_client(test_client)
24+
@pytest.fixture(autouse=True)
25+
def test_client():
26+
client = sentry_sdk.get_current_hub().client
27+
if not client:
28+
client = Client()
29+
sentry_sdk.get_current_hub().bind_client(client)
30+
client._transport = TestTransport()
31+
return client
3232

3333

3434
@pytest.fixture(autouse=True)
@@ -57,7 +57,7 @@ def capture_exception(error=None):
5757

5858

5959
@pytest.fixture
60-
def capture_events(monkeypatch):
60+
def capture_events(test_client, monkeypatch):
6161
events = []
6262
monkeypatch.setattr(test_client, "_transport", TestTransport())
6363
monkeypatch.setattr(test_client._transport, "capture_event", events.append)

tests/integrations/django/myapp/settings.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
# SECURITY WARNING: keep the secret key used in production secret!
3232
SECRET_KEY = "u95e#xr$t3!vdux)fj11!*q*^w^^r#kiyrvt3kjui-t_k%m3op"
3333

34+
SENTRY_FOO = 'yes'
35+
3436
# SECURITY WARNING: don't run with debug turned on in production!
3537
DEBUG = True
3638

0 commit comments

Comments
 (0)