Skip to content

Commit 1f4fa1e

Browse files
committed
update
1 parent 0cde760 commit 1f4fa1e

File tree

3 files changed

+158
-42
lines changed

3 files changed

+158
-42
lines changed
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +0,0 @@
1-
from . import AriadneIntegration, GrapheneIntegration # noqa

sentry_sdk/integrations/graphql/ariadne.py

Lines changed: 149 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from importlib import import_module
2+
13
from sentry_sdk.hub import Hub, _should_send_default_pii
24
from sentry_sdk.integrations import DidNotEnable, Integration
35
from sentry_sdk.utils import parse_version
@@ -9,26 +11,36 @@
911
raise DidNotEnable("graphql-core not installed")
1012

1113
try:
12-
import ariadne
14+
ariadne_graphql = import_module(
15+
"ariadne.graphql"
16+
) # necessary because of some name shadowing shenanigans in ariadne
17+
# XXX verify again
18+
from ariadne.asgi import GraphQL as ASGIGraphQL
19+
from ariadne.asgi.handlers import GraphQLHTTPHandler
20+
from ariadne.asgi.handlers import http as ariadne_http
21+
from ariadne.types import Extension
22+
from ariadne.wsgi import GraphQL as WSGIGraphQL
1323
except ImportError:
1424
raise DidNotEnable("ariadne not installed")
1525

1626

1727
if TYPE_CHECKING:
18-
from typing import Any
19-
from ariadne.graphql import GraphQLSchema
20-
from ariadne.types import GraphQLResult
28+
from typing import Any, List
29+
from ariadne.types import ContextValue, GraphQLError, GraphQLResult, GraphQLSchema
2130
from sentry_sdk._types import EventProcessor
2231

2332

2433
class AriadneIntegration(Integration):
2534
identifier = "ariadne"
2635

36+
# XXX add an option to turn on error monitoring so that people who are instrumenting
37+
# both the server and the client side can turn one of them off if they want
38+
# XXX double patching
39+
2740
@staticmethod
2841
def setup_once():
2942
# type: () -> None
3043
# XXX version guard for ariadne
31-
# guard for double patching
3244
version = parse_version(GRAPHQL_CORE_VERSION)
3345

3446
if version is None:
@@ -39,54 +51,156 @@ def setup_once():
3951
if version < (3, 2):
4052
raise DidNotEnable("graphql-core 3.2 or newer required.")
4153

42-
old_graphql_sync = ariadne.graphql_sync
43-
old_graphql_async = ariadne.graphql
54+
_patch_graphql()
55+
_inject_tracing_extension()
56+
57+
58+
def _patch_graphql():
59+
# type: () -> None
60+
old_graphql_sync = ariadne_graphql.graphql_sync
61+
old_graphql_async = ariadne_graphql.graphql
4462

45-
def _sentry_patched_graphql_sync(schema, data, *args, **kwargs):
46-
# type: (GraphQLSchema, Any, Any, Any) -> GraphQLResult
47-
hub = Hub.current
48-
integration = hub.get_integration(AriadneIntegration)
49-
if integration is None or hub.client is None:
50-
return old_graphql_sync(schema, data, *args, **kwargs)
63+
def _sentry_patched_graphql_sync(schema, data, *args, **kwargs):
64+
# type: (GraphQLSchema, Any, Any, Any) -> GraphQLResult
65+
hub = Hub.current
66+
integration = hub.get_integration(AriadneIntegration)
5167

52-
result = old_graphql_sync(schema, data, *args, **kwargs)
68+
if integration is None or hub.client is None:
69+
return old_graphql_sync(schema, data, *args, **kwargs)
5370

54-
_raise_errors(result, hub)
71+
scope = hub.scope # XXX
72+
if scope is not None:
73+
event_processor = _make_event_processor(schema, data)
74+
scope.add_event_processor(event_processor)
5575

56-
return result
76+
result = old_graphql_sync(schema, data, *args, **kwargs)
77+
78+
_capture_graphql_errors(result, hub)
79+
80+
return result
5781

58-
async def _sentry_patched_graphql_async(schema, data, *args, **kwargs):
59-
# type: (GraphQLSchema, Any, Any, Any) -> GraphQLResult
60-
hub = Hub.current
61-
integration = hub.get_integration(AriadneIntegration)
62-
if integration is None or hub.client is None:
63-
return await old_graphql_async(schema, data, *args, **kwargs)
82+
async def _sentry_patched_graphql_async(schema, data, *args, **kwargs):
83+
# type: (GraphQLSchema, Any, Any, Any) -> GraphQLResult
84+
hub = Hub.current
85+
integration = hub.get_integration(AriadneIntegration)
6486

65-
result = await old_graphql_async(schema, data, *args, **kwargs)
87+
if integration is None or hub.client is None:
88+
return await old_graphql_async(schema, data, *args, **kwargs)
6689

67-
_raise_errors(result, hub)
90+
scope = hub.scope # XXX
91+
if scope is not None:
92+
event_processor = _make_event_processor(schema, data)
93+
scope.add_event_processor(event_processor)
6894

69-
return result
95+
result = await old_graphql_async(schema, data, *args, **kwargs)
96+
# XXX no result available in event processor like this
7097

71-
ariadne.graphql_sync = _sentry_patched_graphql_sync
72-
ariadne.graphql_async = _sentry_patched_graphql_async
98+
# XXX two options.
99+
# a) Handle the exceptions in has_errors. Then it's actual exceptions that
100+
# can be raised properly. But there is no response and it'd have to be
101+
# constructed manually
102+
# b) Handle the exceptions here once they've already been formatted. Then
103+
# we need to reconstruct the exceptions instead before raising them.
104+
# c?) Remember the exception somehow
105+
# d) monkeypatch handle_graphql_errors
73106

107+
_capture_graphql_errors(result, hub)
74108

75-
def _raise_errors(result, hub):
76-
# type: (GraphQLResult, Hub) -> None
77-
data = result[1]
78-
if not isinstance(data, dict) or not data.get("errors"):
79109
return result
80110

81-
for error in data["errors"]:
111+
ariadne_graphql.graphql_sync = _sentry_patched_graphql_sync
112+
ariadne_graphql.graphql = _sentry_patched_graphql_async
113+
ariadne_http.graphql = _sentry_patched_graphql_async
114+
115+
116+
def _inject_tracing_extension():
117+
# type: () -> None
118+
old_asgi_http_handler_init = GraphQLHTTPHandler.__init__
119+
old_asgi_init = ASGIGraphQL.__init__
120+
old_wsgi_init = WSGIGraphQL.__init__
121+
122+
def _sentry_asgi_http_handler_wrapper(self, *args, **kwargs):
123+
# type: (GraphQLHTTPHandler, Any, Any) -> None
124+
extensions = kwargs.get("extensions") or []
125+
extensions.append(SentryTracingExtension)
126+
kwargs["extensions"] = extensions
127+
old_asgi_http_handler_init(self, *args, **kwargs)
128+
129+
def _sentry_asgi_wrapper(self, *args, **kwargs):
130+
# type: (ASGIGraphQL, Any, Any) -> None
131+
http_handler = kwargs.get("http_handler")
132+
if http_handler is None:
133+
http_handler = GraphQLHTTPHandler()
134+
kwargs["http_handler"] = http_handler
135+
old_asgi_init(self, *args, **kwargs)
136+
137+
def _sentry_wsgi_wrapper(self, *args, **kwargs):
138+
# type: (WSGIGraphQL, Any, Any) -> None
139+
extensions = kwargs.get("extensions") or []
140+
extensions.append(SentryTracingExtension)
141+
kwargs["extensions"] = extensions
142+
old_wsgi_init(self, *args, **kwargs)
143+
144+
GraphQLHTTPHandler.__init__ = _sentry_asgi_http_handler_wrapper
145+
ASGIGraphQL.__init__ = _sentry_asgi_wrapper
146+
WSGIGraphQL.__init__ = _sentry_wsgi_wrapper
147+
148+
149+
class SentryTracingExtension(Extension):
150+
def __init__(self):
151+
pass
152+
153+
def request_started(self, context):
154+
# type: (ContextValue) -> None
155+
print("req started ctx", context)
156+
hub = Hub.current
157+
integration = hub.get_integration(AriadneIntegration)
158+
if integration is None:
159+
return
160+
161+
def request_finished(self, context):
162+
# type: (ContextValue) -> None
163+
print("req finished ctx", context)
164+
hub = Hub.current
165+
integration = hub.get_integration(AriadneIntegration)
166+
if integration is None or hub.client is None:
167+
return
168+
169+
def has_errors(self, errors, context):
170+
# type: (List[GraphQLError], ContextValue) -> None
171+
# XXX could use this to capture exceptions but we don't have access
172+
# to the response here
173+
print("has errors ctx", context)
174+
hub = Hub.current
175+
integration = hub.get_integration(AriadneIntegration)
176+
if integration is None or hub.client is None:
177+
return
178+
179+
# XXX def resolve()
180+
181+
182+
def _capture_graphql_errors(result, hub):
183+
result = result[1]
184+
if not isinstance(result, dict):
185+
return
186+
187+
for error in result.get("errors") or []:
188+
# have to reconstruct the exceptions here since they've already been
189+
# formatted
82190
hub.capture_exception(error)
83191

84192

85-
def _make_event_processor():
86-
# type: () -> EventProcessor
193+
def _make_event_processor(schema, data):
194+
# type: (GraphQLSchema, Any) -> EventProcessor
87195
def inner(event, hint):
196+
print("in event processor")
197+
print(schema)
198+
print(data)
88199
if _should_send_default_pii():
89-
pass
200+
if isinstance(data, dict) and data.get("query"):
201+
request_info = event.setdefault("request", {})
202+
request_info["api_target"] = "graphql"
203+
request_info["data"] = str(data["query"])
90204

91205
return event
92206

sentry_sdk/integrations/graphql/graphene.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
class GrapheneIntegration(Integration):
2727
identifier = "graphene"
2828

29+
# XXX add an option to turn on error monitoring so that people who are instrumenting
30+
# both the server and the client side can turn one of them off if they want
31+
2932
@staticmethod
3033
def setup_once():
3134
# type: () -> None
@@ -41,22 +44,22 @@ def setup_once():
4144
raise DidNotEnable("graphql-core 3.2 or newer required.")
4245

4346
# XXX: a guard against patching multiple times?
44-
old_execute_sync = graphene_schema.graphql_sync
45-
old_execute_async = graphene_schema.graphql
47+
old_graphql_sync = graphene_schema.graphql_sync
48+
old_graphql_async = graphene_schema.graphql
4649

4750
def _sentry_patched_graphql_sync(schema, source, *args, **kwargs):
4851
# type: (GraphQLSchema, Union[str, Source], Any, Any) -> ExecutionResult
4952
hub = Hub.current
5053
integration = hub.get_integration(GrapheneIntegration)
5154
if integration is None or hub.client is None:
52-
return old_execute_sync(schema, source, *args, **kwargs)
55+
return old_graphql_sync(schema, source, *args, **kwargs)
5356

5457
scope = hub.scope # XXX
5558
if scope is not None:
5659
event_processor = _make_event_processor(source)
5760
scope.add_event_processor(event_processor)
5861

59-
result = old_execute_sync(schema, source, *args, **kwargs)
62+
result = old_graphql_sync(schema, source, *args, **kwargs)
6063

6164
_raise_graphql_errors(result)
6265

@@ -67,14 +70,14 @@ async def _sentry_patched_graphql_async(schema, source, *args, **kwargs):
6770
hub = Hub.current
6871
integration = hub.get_integration(GrapheneIntegration)
6972
if integration is None or hub.client is None:
70-
return await old_execute_async(schema, source, *args, **kwargs)
73+
return await old_graphql_async(schema, source, *args, **kwargs)
7174

7275
scope = hub.scope # XXX
7376
if scope is not None:
7477
event_processor = _make_event_processor(source)
7578
scope.add_event_processor(event_processor)
7679

77-
result = await old_execute_async(schema, source, *args, **kwargs)
80+
result = await old_graphql_async(schema, source, *args, **kwargs)
7881

7982
_raise_graphql_errors(result)
8083

0 commit comments

Comments
 (0)